2021-09-04 21:29:05 +00:00
|
|
|
// Package secret provides functionality for generating and storing secrets
|
|
|
|
// both in a remote swarm and locally within supported storage such as pass
|
|
|
|
// stores.
|
2021-07-30 20:53:51 +00:00
|
|
|
package secret
|
|
|
|
|
|
|
|
import (
|
2021-07-31 10:47:09 +00:00
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2022-03-12 15:59:45 +00:00
|
|
|
"sync"
|
2021-07-31 10:47:09 +00:00
|
|
|
|
2021-09-05 19:37:03 +00:00
|
|
|
"coopcloud.tech/abra/pkg/client"
|
|
|
|
"coopcloud.tech/abra/pkg/config"
|
2021-07-30 20:53:51 +00:00
|
|
|
"github.com/schultz-is/passgen"
|
2021-09-10 22:54:02 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2021-07-30 20:53:51 +00:00
|
|
|
)
|
|
|
|
|
2021-09-04 21:35:56 +00:00
|
|
|
// secretValue represents a parsed `SECRET_FOO=v1 # length=bar` env var config
|
|
|
|
// secret definition.
|
|
|
|
type secretValue struct {
|
2021-07-31 10:47:09 +00:00
|
|
|
Version string
|
|
|
|
Length int
|
|
|
|
}
|
|
|
|
|
2021-09-04 21:39:38 +00:00
|
|
|
// GeneratePasswords generates passwords.
|
2021-07-30 20:53:51 +00:00
|
|
|
func GeneratePasswords(count, length uint) ([]string, error) {
|
|
|
|
passwords, err := passgen.GeneratePasswords(
|
|
|
|
count,
|
|
|
|
length,
|
|
|
|
passgen.AlphabetDefault,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-25 01:03:09 +00:00
|
|
|
logrus.Debugf("generated %s", strings.Join(passwords, ", "))
|
2021-09-10 22:54:02 +00:00
|
|
|
|
2021-07-30 20:53:51 +00:00
|
|
|
return passwords, nil
|
|
|
|
}
|
|
|
|
|
2021-09-04 21:39:38 +00:00
|
|
|
// GeneratePassphrases generates human readable and rememberable passphrases.
|
2021-07-30 20:53:51 +00:00
|
|
|
func GeneratePassphrases(count uint) ([]string, error) {
|
|
|
|
passphrases, err := passgen.GeneratePassphrases(
|
|
|
|
count,
|
|
|
|
passgen.PassphraseWordCountDefault,
|
|
|
|
rune('-'),
|
|
|
|
passgen.PassphraseCasingDefault,
|
|
|
|
passgen.WordListDefault,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-25 01:03:09 +00:00
|
|
|
logrus.Debugf("generated %s", strings.Join(passphrases, ", "))
|
2021-09-10 22:54:02 +00:00
|
|
|
|
2021-07-30 20:53:51 +00:00
|
|
|
return passphrases, nil
|
|
|
|
}
|
2021-07-31 10:47:09 +00:00
|
|
|
|
2021-09-10 22:54:02 +00:00
|
|
|
// ReadSecretEnvVars reads secret env vars from an app env var config.
|
2021-07-31 10:47:09 +00:00
|
|
|
func ReadSecretEnvVars(appEnv config.AppEnv) map[string]string {
|
|
|
|
secretEnvVars := make(map[string]string)
|
2021-09-10 22:54:02 +00:00
|
|
|
|
2021-07-31 10:47:09 +00:00
|
|
|
for envVar := range appEnv {
|
|
|
|
regex := regexp.MustCompile(`^SECRET.*VERSION.*`)
|
|
|
|
if string(regex.Find([]byte(envVar))) != "" {
|
|
|
|
secretEnvVars[envVar] = appEnv[envVar]
|
|
|
|
}
|
|
|
|
}
|
2021-09-10 22:54:02 +00:00
|
|
|
|
2021-12-25 01:03:09 +00:00
|
|
|
logrus.Debugf("read %s as secrets from %s", secretEnvVars, appEnv)
|
2021-09-10 22:54:02 +00:00
|
|
|
|
2021-07-31 10:47:09 +00:00
|
|
|
return secretEnvVars
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseSecretEnvVarName(secretEnvVar string) string {
|
|
|
|
withoutPrefix := strings.TrimPrefix(secretEnvVar, "SECRET_")
|
|
|
|
withoutSuffix := strings.TrimSuffix(withoutPrefix, "_VERSION")
|
2021-09-10 22:54:02 +00:00
|
|
|
name := strings.ToLower(withoutSuffix)
|
2021-12-25 01:03:09 +00:00
|
|
|
logrus.Debugf("parsed %s as name from %s", name, secretEnvVar)
|
2021-09-10 22:54:02 +00:00
|
|
|
return name
|
2021-07-31 10:47:09 +00:00
|
|
|
}
|
|
|
|
|
2021-08-31 08:31:54 +00:00
|
|
|
func ParseGeneratedSecretName(secret string, appEnv config.App) string {
|
|
|
|
name := fmt.Sprintf("%s_", appEnv.StackName())
|
|
|
|
withoutAppName := strings.TrimPrefix(secret, name)
|
|
|
|
idx := strings.LastIndex(withoutAppName, "_")
|
2021-09-10 22:54:02 +00:00
|
|
|
parsed := withoutAppName[:idx]
|
2021-12-25 01:03:09 +00:00
|
|
|
logrus.Debugf("parsed %s as name from %s", parsed, secret)
|
2021-09-10 22:54:02 +00:00
|
|
|
return parsed
|
2021-08-31 08:31:54 +00:00
|
|
|
}
|
|
|
|
|
2021-09-04 21:35:56 +00:00
|
|
|
func ParseSecretEnvVarValue(secret string) (secretValue, error) {
|
|
|
|
values := strings.Split(secret, "#")
|
2021-07-31 10:47:09 +00:00
|
|
|
if len(values) == 0 {
|
2021-12-25 01:03:09 +00:00
|
|
|
return secretValue{}, fmt.Errorf("unable to parse %s", secret)
|
2021-07-31 10:47:09 +00:00
|
|
|
}
|
2021-09-10 22:54:02 +00:00
|
|
|
|
2021-07-31 10:47:09 +00:00
|
|
|
if len(values) == 1 {
|
2021-09-04 21:35:56 +00:00
|
|
|
return secretValue{Version: values[0], Length: 0}, nil
|
2021-07-31 10:47:09 +00:00
|
|
|
}
|
2021-09-10 22:54:02 +00:00
|
|
|
|
|
|
|
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], " ", "")
|
|
|
|
|
2021-12-25 01:03:09 +00:00
|
|
|
logrus.Debugf("parsed version %s and length '%v' from %s", version, length, secret)
|
2021-09-10 22:54:02 +00:00
|
|
|
|
|
|
|
return secretValue{Version: version, Length: length}, nil
|
2021-07-31 10:47:09 +00:00
|
|
|
}
|
|
|
|
|
2021-09-04 21:39:38 +00:00
|
|
|
// GenerateSecrets generates secrets locally and sends them to a remote server for storage.
|
2021-07-31 13:50:04 +00:00
|
|
|
func GenerateSecrets(secretEnvVars map[string]string, appName, server string) (map[string]string, error) {
|
2021-07-31 10:47:09 +00:00
|
|
|
secrets := make(map[string]string)
|
|
|
|
|
2022-03-12 15:59:45 +00:00
|
|
|
var mutex sync.Mutex
|
|
|
|
var wg sync.WaitGroup
|
2021-07-31 13:50:04 +00:00
|
|
|
ch := make(chan error, len(secretEnvVars))
|
2021-07-31 10:47:09 +00:00
|
|
|
for secretEnvVar := range secretEnvVars {
|
2022-03-12 15:59:45 +00:00
|
|
|
wg.Add(1)
|
|
|
|
|
2021-07-31 13:50:04 +00:00
|
|
|
go func(s string) {
|
2022-03-12 15:59:45 +00:00
|
|
|
defer wg.Done()
|
|
|
|
|
2021-07-31 13:50:04 +00:00
|
|
|
secretName := ParseSecretEnvVarName(s)
|
|
|
|
secretValue, err := ParseSecretEnvVarValue(secretEnvVars[s])
|
2021-07-31 10:47:09 +00:00
|
|
|
if err != nil {
|
2021-07-31 13:50:04 +00:00
|
|
|
ch <- err
|
|
|
|
return
|
2021-07-31 10:47:09 +00:00
|
|
|
}
|
2022-03-12 15:59:45 +00:00
|
|
|
|
2021-07-31 13:50:04 +00:00
|
|
|
secretRemoteName := fmt.Sprintf("%s_%s_%s", appName, secretName, secretValue.Version)
|
2021-12-25 01:03:09 +00:00
|
|
|
logrus.Debugf("attempting to generate and store %s on %s", secretRemoteName, server)
|
2022-03-12 15:59:45 +00:00
|
|
|
|
2021-07-31 13:50:04 +00:00
|
|
|
if secretValue.Length > 0 {
|
|
|
|
passwords, err := GeneratePasswords(1, uint(secretValue.Length))
|
|
|
|
if err != nil {
|
|
|
|
ch <- err
|
|
|
|
return
|
|
|
|
}
|
2022-03-12 15:59:45 +00:00
|
|
|
|
2021-07-31 13:50:04 +00:00
|
|
|
if err := client.StoreSecret(secretRemoteName, passwords[0], server); err != nil {
|
2021-12-13 11:29:26 +00:00
|
|
|
if strings.Contains(err.Error(), "AlreadyExists") {
|
|
|
|
logrus.Warnf("%s already exists, moving on...", secretRemoteName)
|
|
|
|
ch <- nil
|
|
|
|
} else {
|
|
|
|
ch <- err
|
|
|
|
}
|
2021-07-31 13:50:04 +00:00
|
|
|
return
|
|
|
|
}
|
2022-03-12 15:59:45 +00:00
|
|
|
|
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
2021-07-31 13:50:04 +00:00
|
|
|
secrets[secretName] = passwords[0]
|
|
|
|
} else {
|
|
|
|
passphrases, err := GeneratePassphrases(1)
|
|
|
|
if err != nil {
|
|
|
|
ch <- err
|
|
|
|
return
|
|
|
|
}
|
2022-03-12 15:59:45 +00:00
|
|
|
|
2021-07-31 13:50:04 +00:00
|
|
|
if err := client.StoreSecret(secretRemoteName, passphrases[0], server); err != nil {
|
2021-12-13 11:29:26 +00:00
|
|
|
if strings.Contains(err.Error(), "AlreadyExists") {
|
|
|
|
logrus.Warnf("%s already exists, moving on...", secretRemoteName)
|
|
|
|
ch <- nil
|
|
|
|
} else {
|
|
|
|
ch <- err
|
|
|
|
}
|
|
|
|
return
|
2021-07-31 13:50:04 +00:00
|
|
|
}
|
2022-03-12 15:59:45 +00:00
|
|
|
|
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
2021-07-31 13:50:04 +00:00
|
|
|
secrets[secretName] = passphrases[0]
|
2021-07-31 10:47:09 +00:00
|
|
|
}
|
2021-07-31 13:50:04 +00:00
|
|
|
ch <- nil
|
|
|
|
}(secretEnvVar)
|
|
|
|
}
|
|
|
|
|
2022-03-12 15:59:45 +00:00
|
|
|
wg.Wait()
|
|
|
|
|
2021-07-31 13:50:04 +00:00
|
|
|
for range secretEnvVars {
|
|
|
|
err := <-ch
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-07-31 10:47:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-25 01:03:09 +00:00
|
|
|
logrus.Debugf("generated and stored %s on %s", secrets, server)
|
2021-09-10 22:54:02 +00:00
|
|
|
|
2021-07-31 10:47:09 +00:00
|
|
|
return secrets, nil
|
|
|
|
}
|