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:
parent
827696f6a2
commit
2b9a0815ca
4 changed files with 91 additions and 22 deletions
10
README.md
10
README.md
|
@ -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" = {};
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue