1
0
Fork 0
mirror of https://github.com/Mic92/sops-nix.git synced 2024-12-14 11:57:52 +00:00

Implement nested secrets

This commit is contained in:
Janne Heß 2021-09-10 12:02:38 +02:00
parent 827696f6a2
commit 2b9a0815ca
No known key found for this signature in database
GPG key ID: 69165158F05265DF
4 changed files with 91 additions and 22 deletions

View file

@ -352,8 +352,13 @@ In our example we put the following content in it:
example-key: example-value
```
NOTE: At the moment we do not support nested data structures that
sops support. This might change in the future. See also [Different file formats](#different-file-formats)
Nesting the key results in the creation of directories.
These directories will be owned by root:keys and have permissions 0751.
```yaml
myservice:
my_subdir:
my_secret: example value
```
As a result when saving the file the following content will be in it:
@ -443,6 +448,7 @@ If you derived your server public key from ssh, all you need in your configurati
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
# This will generate a new key if the key specified above does not exist
sops.age.generateKey = true;
sops.secrets."myservice/my_subdir/my_secret" = {};
}
```

View file

@ -205,6 +205,54 @@ type plainData struct {
binary []byte
}
func recurseSecretKey(keys map[string]interface{}, wantedKey string) (string, error) {
var val interface{}
var ok bool
currentKey := wantedKey
currentData := keys
keyUntilNow := ""
for {
slashIndex := strings.IndexByte(currentKey, '/')
if slashIndex == -1 {
// We got to the end
val, ok = currentData[currentKey]
if !ok {
if keyUntilNow != "" {
keyUntilNow += "."
}
return "", fmt.Errorf("b: The key '%s%s' cannot be found", keyUntilNow, currentKey)
}
break
}
thisKey := currentKey[:slashIndex]
if keyUntilNow == "" {
keyUntilNow = thisKey
} else {
keyUntilNow += "." + thisKey
}
currentKey = currentKey[(slashIndex + 1):]
val, ok = currentData[thisKey]
if !ok {
return "", fmt.Errorf("The key '%s' cannot be found", keyUntilNow)
}
valWithWrongType, ok := val.(map[interface{}]interface{})
if !ok {
return "", fmt.Errorf("Key '%s' does not refer to a dictionary", keyUntilNow)
}
currentData = make(map[string]interface{})
for key, value := range valWithWrongType {
currentData[key.(string)] = value
}
}
strVal, ok := val.(string)
if !ok {
return "", fmt.Errorf("The value of key '%s' is not a string", keyUntilNow)
}
return strVal, nil
}
func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
sourceFile := sourceFiles[s.SopsFile]
if sourceFile.data == nil || sourceFile.binary == nil {
@ -229,14 +277,9 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
if s.Format == Binary {
s.value = sourceFile.binary
} else {
val, ok := sourceFile.data[s.Key]
if !ok {
return fmt.Errorf("The key '%s' cannot be found in '%s'", s.Key, s.SopsFile)
}
strVal, ok := val.(string)
if !ok {
return fmt.Errorf("The value of key '%s' in '%s' is not a string", s.Key, s.SopsFile)
strVal, err := recurseSecretKey(sourceFile.data, s.Key)
if err != nil {
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err)
}
s.value = []byte(strVal)
}
@ -308,14 +351,27 @@ func prepareSecretsDir(secretMountpoint string, linkName string, keysGid int) (*
return &dir, nil
}
func writeSecrets(secretDir string, secrets []secret) error {
func writeSecrets(secretDir string, secrets []secret, keysGid int) error {
for _, secret := range secrets {
filepath := filepath.Join(secretDir, secret.Name)
if err := ioutil.WriteFile(filepath, []byte(secret.value), secret.mode); err != nil {
return fmt.Errorf("Cannot write %s: %w", filepath, err)
fp := filepath.Join(secretDir, secret.Name)
dirs := strings.Split(filepath.Dir(secret.Name), "/")
pathSoFar := secretDir
for _, dir := range dirs {
pathSoFar = filepath.Join(pathSoFar, dir)
if err := os.MkdirAll(pathSoFar, 0751); err != nil {
return fmt.Errorf("Cannot create directory '%s' for %s: %w", pathSoFar, fp, err)
}
if err := os.Chown(pathSoFar, 0, int(keysGid)); err != nil {
return fmt.Errorf("Cannot own directory '%s' for %s: %w", pathSoFar, fp, err)
}
}
if err := os.Chown(filepath, secret.owner, secret.group); err != nil {
return fmt.Errorf("Cannot change owner/group of '%s' to %d/%d: %w", filepath, secret.owner, secret.group, err)
if err := ioutil.WriteFile(fp, []byte(secret.value), secret.mode); err != nil {
return fmt.Errorf("Cannot write %s: %w", fp, err)
}
if err := os.Chown(fp, secret.owner, secret.group); err != nil {
return fmt.Errorf("Cannot change owner/group of '%s' to %d/%d: %w", fp, secret.owner, secret.group, err)
}
}
return nil
@ -374,8 +430,9 @@ func (app *appContext) validateSopsFile(s *secret, file *secretFile) error {
file.firstSecret.Format, file.firstSecret.Name)
}
if app.checkMode != Manifest && s.Format != Binary {
if _, ok := file.keys[s.Key]; !ok {
return fmt.Errorf("secret %s with the key %s not found in %s", s.Name, s.Key, s.SopsFile)
_, err := recurseSecretKey(file.keys, s.Key)
if err != nil {
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err)
}
}
return nil
@ -680,7 +737,7 @@ func installSecrets(args []string) error {
if err != nil {
return fmt.Errorf("Failed to prepare new secrets directory: %w", err)
}
if err := writeSecrets(*secretDir, manifest.Secrets); err != nil {
if err := writeSecrets(*secretDir, manifest.Secrets, keysGid); err != nil {
return fmt.Errorf("Cannot write secrets: %w", err)
}
if err := symlinkSecrets(manifest.SymlinkPath, manifest.Secrets); err != nil {

View file

@ -87,6 +87,7 @@
sops.gnupg.home = "/run/gpghome";
sops.defaultSopsFile = ./test-assets/secrets.yaml;
sops.secrets.test_key.owner = config.users.users.someuser.name;
sops.secrets."nested/test/file".owner = config.users.users.someuser.name;
sops.secrets.existing-file = {
key = "test_key";
path = "/run/existing-file";
@ -118,6 +119,8 @@
assertEqual("test_value", value)
server.succeed("runuser -u someuser -- cat /run/secrets/test_key >&2")
value = server.succeed("cat /run/secrets/nested/test/file")
assertEqual(value, "another value")
target = server.succeed("readlink -f /run/existing-file")
assertEqual("/run/secrets.d/1/existing-file", target.strip())

View file

@ -2,6 +2,9 @@ test_key: ENC[AES256_GCM,data:2mP+IAdczoEr0g==,iv:voX4IQemcgt0O97oLExy5r2V85nn68
a_list:
- ENC[AES256_GCM,data:oOQ=,iv:5P+1UQyIYOW8xXgsvTXC17msGcA6IGB3N8n+pstfqjo=,tag:ox4rgjbb8c0vYZ2XmwRgpg==,type:str]
- ENC[AES256_GCM,data:mYU=,iv:LbGS8DjM6Vnr2nU7QokzQlg0gL+XMWhqbN+ypP7ZIZo=,tag:CFrhnZv6lYGJOVso+2YBFg==,type:str]
nested:
test:
file: ENC[AES256_GCM,data:9YhsaXoxxdbLUzeWqA==,iv:xBYQQpsC/Xq0wHXNcqmLxNs5yvG+yjBzcpdRpC+UJxs=,tag:OnonR98dn9CXh/LwlRwXIw==,type:str]
sops:
kms: []
gcp_kms: []
@ -26,8 +29,8 @@ sops:
WHJmNkhFVStxRG5PZTZUWnRFTmtzemsKLXKJN3GSJKDI4MYPxDU5HbTzoSAt0jK9
T9sJbd++By2OC9rl+GJoJcy4aM0uTYy83EDfqBV02Y1CfepRjHLRWQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2021-08-27T17:58:39Z"
mac: ENC[AES256_GCM,data:V9QGBTrofAza2LK1hA5cQmuT37BsfRJZZtSmvc5CDnIeWYTLUOkCFRzY+wk3uZNj0aM3BUAjvIM6LPNJ+rR8p0vXwKN37UH/oRmGADuLcu5Ec7hmArCGjKMo0gyaarlvuGmFhjb1gcW2PAbo84lDykxbTyf7Pp7APJhIvGbwBu4=,iv:ufG8sG5NAtVO25kZXrWnSQ/kkbDlwmOWhO4T9UpRvOg=,tag:YidkKTBqH48N7bjBabrAgQ==,type:str]
lastmodified: "2021-09-30T19:49:41Z"
mac: ENC[AES256_GCM,data:WQjpNaji5Jg/0m0brFXernN+n+zwroiyFRGmhXE2T13THjnxlPyTxkjTIf3oJlAwpeIsfaCCxRdBSJC1B9s3XaqCWHTY4wEOdZ5f+oNBlMxbwKyN5t+GG2CRdpoMsIQ/bBJ+v8LM5BEa3qNXQ6TwWrnhAXWWPRjRa1LsguvI+TM=,iv:fwWjzvLA2cYJNnanm/vw5yE1tXtGU2amZfhw3Ha5zbo=,tag:6FT1M6fqJfUX8E10pvEehw==,type:str]
pgp:
- created_at: "2020-07-12T08:03:51Z"
enc: |
@ -63,4 +66,4 @@ sops:
-----END PGP MESSAGE-----
fp: 2504791468B153B8A3963CC97BA53D1919C5DFD4
unencrypted_suffix: _unencrypted
version: 3.6.1
version: 3.7.1