package secret import ( "errors" "fmt" "os/exec" "regexp" "strconv" "strings" "coopcloud.tech/abra/client" "coopcloud.tech/abra/config" "github.com/schultz-is/passgen" ) type SecretValue struct { Version string Length int } func GeneratePasswords(count, length uint) ([]string, error) { passwords, err := passgen.GeneratePasswords( count, length, passgen.AlphabetDefault, ) if err != nil { return nil, err } return passwords, nil } func GeneratePassphrases(count uint) ([]string, error) { passphrases, err := passgen.GeneratePassphrases( count, passgen.PassphraseWordCountDefault, rune('-'), passgen.PassphraseCasingDefault, passgen.WordListDefault, ) if err != nil { return nil, err } return passphrases, nil } func ReadSecretEnvVars(appEnv config.AppEnv) map[string]string { secretEnvVars := make(map[string]string) for envVar := range appEnv { regex := regexp.MustCompile(`^SECRET.*VERSION.*`) if string(regex.Find([]byte(envVar))) != "" { secretEnvVars[envVar] = appEnv[envVar] } } return secretEnvVars } func ParseSecretEnvVarName(secretEnvVar string) string { withoutPrefix := strings.TrimPrefix(secretEnvVar, "SECRET_") withoutSuffix := strings.TrimSuffix(withoutPrefix, "_VERSION") return strings.ToLower(withoutSuffix) } func ParseSecretEnvVarValue(secretValue string) (SecretValue, error) { values := strings.Split(secretValue, "#") if len(values) == 0 { return SecretValue{}, fmt.Errorf("unable to parse '%s'", secretValue) } if len(values) == 1 { return SecretValue{Version: values[0], Length: 0}, nil } else { split := strings.Split(values[1], "=") parsed := split[len(split)-1] stripped := strings.ReplaceAll(parsed, " ", "") length, err := strconv.Atoi(stripped) if err != nil { return SecretValue{}, err } version := strings.ReplaceAll(values[0], " ", "") return SecretValue{Version: version, Length: length}, nil } } func GenerateSecrets(secretEnvVars map[string]string, appName, server string) (map[string]string, error) { secrets := make(map[string]string) ch := make(chan error, len(secretEnvVars)) for secretEnvVar := range secretEnvVars { go func(s string) { secretName := ParseSecretEnvVarName(s) secretValue, err := ParseSecretEnvVarValue(secretEnvVars[s]) if err != nil { ch <- err return } secretRemoteName := fmt.Sprintf("%s_%s_%s", appName, secretName, secretValue.Version) if secretValue.Length > 0 { passwords, err := GeneratePasswords(1, uint(secretValue.Length)) if err != nil { ch <- err return } if err := client.StoreSecret(secretRemoteName, passwords[0], server); err != nil { ch <- err return } secrets[secretName] = passwords[0] } else { passphrases, err := GeneratePassphrases(1) if err != nil { ch <- err return } if err := client.StoreSecret(secretRemoteName, passphrases[0], server); err != nil { ch <- err } secrets[secretName] = passphrases[0] } ch <- nil }(secretEnvVar) } for range secretEnvVars { err := <-ch if err != nil { return nil, err } } return secrets, nil } func PassInsertSecret(secretValue, secretName, appName, server string) error { _, err := exec.LookPath("pass") if err != nil { return errors.New("pass cannot be found on your $PATH, is it installed?") } cmd := fmt.Sprintf( "echo %s | pass insert hosts/%s/%s/%s -m", secretValue, server, appName, secretName, ) if err := exec.Command("bash", "-c", cmd).Run(); err != nil { return err } return nil }