abra/secret/secret.go

153 lines
3.5 KiB
Go

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
}