add charset modifier to secret generation (#521)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
since we need special chars in passwords for a recipe we are working on, i have added the option to specify a charset in the same way as the length can be setted. i did not change anything in the behaviour, so if length is not specified, the charset gets ignored whether it is there or not. you can specify the following: `charset=default` - Results in passgen.AlphabetDefault being used `charset=special` - Results in passgen.AlphabetSpecial being used `charset=safespecial` - Results in `!@#%^&*_-+=` being used (so it is AlphabetSpecial without the dollar sign) `charset=default,special` or `charset=special,default` - Results in passgen.AlphabetDefault + passgen.AlphabetSpecial being used `charset=default,safespecial` or `charset=safespecial,default` - Results in passgen.AlphabetDefault + `!@#%^&*_-+=` being used ((so it is AlphabetSpecial without the dollar sign) PR for the docs: toolshed/docs.coopcloud.tech#271 Co-authored-by: p4u1 <p4u1@noreply.git.coopcloud.tech> Reviewed-on: #521 Reviewed-by: p4u1 <p4u1@noreply.git.coopcloud.tech> Co-authored-by: Apfelwurm <Alexander@volzit.de> Co-committed-by: Apfelwurm <Alexander@volzit.de>
This commit is contained in:
parent
80ad6c6681
commit
d0f982456e
@ -33,6 +33,10 @@ type Secret struct {
|
||||
// variable. For Example:
|
||||
// SECRET_FOO=v1 # length=12
|
||||
Length int
|
||||
// Charset comes from the charset modifier at the secret version environment
|
||||
// variable. For Example:
|
||||
// SECRET_FOO=v1 # charset=default,special
|
||||
Charset string
|
||||
// RemoteName is the name of the secret on the server. For example:
|
||||
// name: ${STACK_NAME}_test_pass_two_${SECRET_TEST_PASS_TWO_VERSION}
|
||||
// With the following:
|
||||
@ -43,38 +47,38 @@ type Secret struct {
|
||||
RemoteName string
|
||||
}
|
||||
|
||||
// GeneratePasswords generates passwords.
|
||||
func GeneratePasswords(count, length uint) ([]string, error) {
|
||||
// GeneratePassword generates passwords.
|
||||
func GeneratePassword(length uint, charset string) (string, error) {
|
||||
passwords, err := passgen.GeneratePasswords(
|
||||
count,
|
||||
1,
|
||||
length,
|
||||
passgen.AlphabetDefault,
|
||||
charset,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debugf("generated %s", strings.Join(passwords, ", "))
|
||||
|
||||
return passwords, nil
|
||||
return passwords[0], nil
|
||||
}
|
||||
|
||||
// GeneratePassphrases generates human readable and rememberable passphrases.
|
||||
func GeneratePassphrases(count uint) ([]string, error) {
|
||||
// GeneratePassphrase generates human readable and rememberable passphrases.
|
||||
func GeneratePassphrase() (string, error) {
|
||||
passphrases, err := passgen.GeneratePassphrases(
|
||||
count,
|
||||
1,
|
||||
passgen.PassphraseWordCountDefault,
|
||||
rune('-'),
|
||||
passgen.PassphraseCasingDefault,
|
||||
passgen.WordListDefault,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debugf("generated %s", strings.Join(passphrases, ", "))
|
||||
|
||||
return passphrases, nil
|
||||
return passphrases[0], nil
|
||||
}
|
||||
|
||||
// ReadSecretsConfig reads secret names/versions from the recipe config. The
|
||||
@ -150,6 +154,8 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
||||
}
|
||||
value.Length = length
|
||||
}
|
||||
|
||||
value.Charset = resolveCharset(modifierValues["charset"])
|
||||
break
|
||||
}
|
||||
secretValues[secretId] = value
|
||||
@ -158,6 +164,22 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
||||
return secretValues, nil
|
||||
}
|
||||
|
||||
// resolveCharset sets the passgen Alphabet required for a secret
|
||||
func resolveCharset(input string) string {
|
||||
switch strings.ToLower(input) {
|
||||
case "special":
|
||||
return passgen.AlphabetSpecial
|
||||
case "safespecial":
|
||||
return "!@#%^&*_-+="
|
||||
case "default,special", "special,default":
|
||||
return passgen.AlphabetDefault + passgen.AlphabetSpecial
|
||||
case "default,safespecial", "safespecial,default":
|
||||
return passgen.AlphabetDefault + "!@#%^&*_-+="
|
||||
default:
|
||||
return passgen.AlphabetDefault // Fallback to default
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSecrets generates secrets locally and sends them to a remote server for storage.
|
||||
func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server string) (map[string]string, error) {
|
||||
secretsGenerated := map[string]string{}
|
||||
@ -173,13 +195,13 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
||||
log.Debugf("attempting to generate and store %s on %s", secret.RemoteName, server)
|
||||
|
||||
if secret.Length > 0 {
|
||||
passwords, err := GeneratePasswords(1, uint(secret.Length))
|
||||
password, err := GeneratePassword(uint(secret.Length), secret.Charset)
|
||||
if err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
|
||||
if err := client.StoreSecret(cl, secret.RemoteName, passwords[0], server); err != nil {
|
||||
if err := client.StoreSecret(cl, secret.RemoteName, password, server); err != nil {
|
||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||
log.Warnf("%s already exists", secret.RemoteName)
|
||||
ch <- nil
|
||||
@ -191,15 +213,15 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
secretsGenerated[secretName] = passwords[0]
|
||||
secretsGenerated[secretName] = password
|
||||
} else {
|
||||
passphrases, err := GeneratePassphrases(1)
|
||||
passphrase, err := GeneratePassphrase()
|
||||
if err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
|
||||
if err := client.StoreSecret(cl, secret.RemoteName, passphrases[0], server); err != nil {
|
||||
if err := client.StoreSecret(cl, secret.RemoteName, passphrase, server); err != nil {
|
||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||
log.Warnf("%s already exists", secret.RemoteName)
|
||||
ch <- nil
|
||||
@ -211,7 +233,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
secretsGenerated[secretName] = passphrases[0]
|
||||
secretsGenerated[secretName] = passphrase
|
||||
}
|
||||
ch <- nil
|
||||
}(n, v)
|
||||
|
@ -17,16 +17,37 @@ func TestReadSecretsConfig(t *testing.T) {
|
||||
assert.Equal(t, "test_example_com_test_pass_one_v2", secretsFromConfig["test_pass_one"].RemoteName)
|
||||
assert.Equal(t, "v2", secretsFromConfig["test_pass_one"].Version)
|
||||
assert.Equal(t, 0, secretsFromConfig["test_pass_one"].Length)
|
||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789", secretsFromConfig["test_pass_one"].Charset)
|
||||
|
||||
// Has a length modifier
|
||||
assert.Equal(t, "test_example_com_test_pass_two_v1", secretsFromConfig["test_pass_two"].RemoteName)
|
||||
assert.Equal(t, "v1", secretsFromConfig["test_pass_two"].Version)
|
||||
assert.Equal(t, 10, secretsFromConfig["test_pass_two"].Length)
|
||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789", secretsFromConfig["test_pass_two"].Charset)
|
||||
|
||||
// Secret name does not include the secret id
|
||||
assert.Equal(t, "test_example_com_pass_three_v2", secretsFromConfig["test_pass_three"].RemoteName)
|
||||
assert.Equal(t, "v2", secretsFromConfig["test_pass_three"].Version)
|
||||
assert.Equal(t, 0, secretsFromConfig["test_pass_three"].Length)
|
||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789", secretsFromConfig["test_pass_three"].Charset)
|
||||
|
||||
// Has a length modifier and a charset=default,safespecial modifier
|
||||
assert.Equal(t, "test_example_com_test_pass_four_v1", secretsFromConfig["test_pass_four"].RemoteName)
|
||||
assert.Equal(t, "v1", secretsFromConfig["test_pass_four"].Version)
|
||||
assert.Equal(t, 12, secretsFromConfig["test_pass_four"].Length)
|
||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#%^&*_-+=", secretsFromConfig["test_pass_four"].Charset)
|
||||
|
||||
// Has a length modifier and a charset=default,special modifier
|
||||
assert.Equal(t, "test_example_com_test_pass_five_v1", secretsFromConfig["test_pass_five"].RemoteName)
|
||||
assert.Equal(t, "v1", secretsFromConfig["test_pass_five"].Version)
|
||||
assert.Equal(t, 12, secretsFromConfig["test_pass_five"].Length)
|
||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*_-+=", secretsFromConfig["test_pass_five"].Charset)
|
||||
|
||||
// Has only a charset=default,special modifier, which gets setted but ignored in the generation
|
||||
assert.Equal(t, "test_example_com_test_pass_six_v1", secretsFromConfig["test_pass_six"].RemoteName)
|
||||
assert.Equal(t, "v1", secretsFromConfig["test_pass_six"].Version)
|
||||
assert.Equal(t, 0, secretsFromConfig["test_pass_six"].Length)
|
||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*_-+=", secretsFromConfig["test_pass_six"].Charset)
|
||||
}
|
||||
|
||||
func TestReadSecretsConfigWithLongDomain(t *testing.T) {
|
||||
|
@ -1,3 +1,6 @@
|
||||
SECRET_TEST_PASS_ONE_VERSION=v2
|
||||
SECRET_TEST_PASS_TWO_VERSION=v1 # length=10
|
||||
SECRET_TEST_PASS_THREE_VERSION=v2
|
||||
SECRET_TEST_PASS_FOUR_VERSION=v1 # length=12 charset=default,safespecial
|
||||
SECRET_TEST_PASS_FIVE_VERSION=v1 # length=12 charset=default,special
|
||||
SECRET_TEST_PASS_SIX_VERSION=v1 # charset=default,special
|
||||
|
@ -8,6 +8,9 @@ services:
|
||||
- test_pass_one
|
||||
- test_pass_two
|
||||
- test_pass_three
|
||||
- test_pass_four
|
||||
- test_pass_five
|
||||
- test_pass_six
|
||||
|
||||
secrets:
|
||||
test_pass_one:
|
||||
@ -19,3 +22,12 @@ secrets:
|
||||
test_pass_three:
|
||||
external: true
|
||||
name: ${STACK_NAME}_pass_three_${SECRET_TEST_PASS_THREE_VERSION} # secretId and name don't match
|
||||
test_pass_four:
|
||||
external: true
|
||||
name: ${STACK_NAME}_test_pass_four_${SECRET_TEST_PASS_FOUR_VERSION}
|
||||
test_pass_five:
|
||||
external: true
|
||||
name: ${STACK_NAME}_test_pass_five_${SECRET_TEST_PASS_FIVE_VERSION}
|
||||
test_pass_six:
|
||||
external: true
|
||||
name: ${STACK_NAME}_test_pass_six_${SECRET_TEST_PASS_SIX_VERSION}
|
||||
|
Loading…
x
Reference in New Issue
Block a user