package main import ( "fmt" "os" "strings" vault "github.com/hashicorp/vault/api" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) const ( eol = "\n" multiLineFileDelim = "_GitHubActionsFileCommandDelimeter_" multilineFileCmd = "%s<<" + multiLineFileDelim + eol + "%s" + eol + multiLineFileDelim // ${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter} ) var vaultClient *vault.Client func main() { var err error logrus.Printf("Environment: %s", os.Environ()) vaultClient, err = vault.NewClient(&vault.Config{ Address: getInput("url"), }) if err != nil { logrus.WithError(err).Fatal("error creating vault client") } switch getInput("method") { case "approle": if err := setVaultTokenFromRoleID(); err != nil { logrus.WithError(err).Fatal("error setting vault token from role id") } case "token": vaultClient.SetToken(getInput("token")) default: logrus.Fatal("no credentials found") } exprs := strings.Split(getInput("secrets"), ";") for _, expr := range exprs { p, k, o := parseExpression(strings.TrimSpace(expr)) logrus.Infof("%q => %q => %q", p, k, o) s, err := getVaultSecretKey(p, k) if err != nil { logrus.WithError(err).Fatal("error reading credential") } setOutput(o, s) } } func parseExpression(i string) (path, key, outputName string) { input := strings.TrimSpace(strings.Trim(i, "\"")) if strings.Contains(input, "|") { oSplit := strings.Split(strings.TrimSpace(input), "|") if len(oSplit) > 1 { outputName = strings.TrimSpace(oSplit[1]) input = strings.TrimSpace(oSplit[0]) } } if strings.Contains(input, " ") { iSplit := strings.Split(strings.TrimSpace(input), " ") if len(iSplit) > 1 { path = strings.TrimSpace(iSplit[0]) key = strings.TrimSpace(iSplit[1]) if outputName == "" { outputName = strings.TrimSpace(iSplit[1]) } } } outputName = strings.TrimSpace(outputName) outputName = strings.ReplaceAll(outputName, " ", "_") outputName = strings.ToUpper(outputName) return path, key, outputName } func getVaultSecretKey(p, k string) (string, error) { s, err := getVaultSecret(p) if err != nil { return "", errors.Wrap(err, "error getting path") } if s == nil || s.Data == nil || s.Data["data"] == nil { return "", errors.New("nil secret or secret data") } v, ok := s.Data["data"].(map[string]any)[k].(string) if !ok { logrus.Infof("Data: %#v", s.Data["data"]) return "", errors.New("key in secret not found") } return v, nil } func getVaultSecret(p string) (*vault.Secret, error) { return vaultClient.Logical().Read(p) } func setVaultTokenFromRoleID() error { data := map[string]any{ "role_id": getInput("roleid"), } if getInput("secretid") != "" { data["secret_id"] = getInput("secretid") } loginSecret, err := vaultClient.Logical().Write("auth/approle/login", data) if err != nil || loginSecret.Auth == nil { return errors.Wrap(err, "fetching authentication token") } vaultClient.SetToken(loginSecret.Auth.ClientToken) return nil } func getInput(i string) string { e := strings.ReplaceAll(i, " ", "_") e = strings.ToUpper(e) e = "INPUT_" + e return strings.ReplaceAll(strings.TrimSpace(os.Getenv(e)), "\\n", "\n") } func setOutput(k, v string) (err error) { msg := fmt.Sprintf(multilineFileCmd, k, v) outputFilepath := os.Getenv("GITHUB_OUTPUT") f, err := os.OpenFile(outputFilepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return errors.Wrap(err, "open output file") } defer func() { if cErr := f.Close(); cErr != nil && err == nil { err = cErr } }() if _, err := f.Write([]byte(msg)); err != nil { return errors.Wrap(err, "write to output") } os.Setenv(k, v) return nil }