From 2bac6284aa762b74784446b3687713e4a3be9a52 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Mon, 8 May 2023 15:38:32 -0400 Subject: [PATCH 1/3] Allow nested secrets in secrets.json Prior to this change, if secrets.json had nested secrets we would see this error (example): ``` sops-install-secrets: Manifest is not valid: secret jenkins-nix-ci/cachix-auth-token/description in /nix/store/wxm763za3rbrpiijfbgss9g5ll0sd29z-secrets.json is not valid: Key 'jenkins-nix-ci' does not refer to a dictionary ``` The reason is that introspecting the map key to be `interface` fails, when it is in fact a string. --- pkgs/sops-install-secrets/main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index fc32f698..8c3ff77b 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -10,6 +10,7 @@ import ( "os/user" "path" "path/filepath" + "reflect" "strconv" "strings" "syscall" @@ -239,13 +240,13 @@ func recurseSecretKey(keys map[string]interface{}, wantedKey string) (string, er if !ok { return "", fmt.Errorf("The key '%s' cannot be found", keyUntilNow) } - valWithWrongType, ok := val.(map[interface{}]interface{}) + valWithWrongType, ok := val.(map[string]interface{}) if !ok { - return "", fmt.Errorf("Key '%s' does not refer to a dictionary", keyUntilNow) + return "", fmt.Errorf("Expected key '%s' to refer to a dictionary, but it is a '%s' instead", keyUntilNow, reflect.TypeOf(val).Elem()) } currentData = make(map[string]interface{}) for key, value := range valWithWrongType { - currentData[key.(string)] = value + currentData[key] = value } } From d4a42a9ee494f7ac57478b574c85f60a496cf2dd Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Mon, 8 May 2023 15:49:13 -0400 Subject: [PATCH 2/3] Show the key type in error message --- pkgs/sops-install-secrets/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index 8c3ff77b..90e01d15 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -242,7 +242,7 @@ func recurseSecretKey(keys map[string]interface{}, wantedKey string) (string, er } valWithWrongType, ok := val.(map[string]interface{}) if !ok { - return "", fmt.Errorf("Expected key '%s' to refer to a dictionary, but it is a '%s' instead", keyUntilNow, reflect.TypeOf(val).Elem()) + return "", fmt.Errorf("Expected string key '%s' to refer to a dictionary; but we have map[%s][%s]", keyUntilNow, reflect.TypeOf(val).Key(), reflect.TypeOf(val).Elem()) } currentData = make(map[string]interface{}) for key, value := range valWithWrongType { From f4b1471b239dcc55cd8e8a8f3fc410c73511e62b Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Mon, 8 May 2023 16:07:29 -0400 Subject: [PATCH 3/3] recurseSecretKey: handle json and yaml differently Because the map's key type is different for both. --- pkgs/sops-install-secrets/main.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index 90e01d15..dfdd5117 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -209,7 +209,7 @@ type plainData struct { binary []byte } -func recurseSecretKey(keys map[string]interface{}, wantedKey string) (string, error) { +func recurseSecretKey(format FormatType, keys map[string]interface{}, wantedKey string) (string, error) { var val interface{} var ok bool currentKey := wantedKey @@ -240,13 +240,25 @@ func recurseSecretKey(keys map[string]interface{}, wantedKey string) (string, er if !ok { return "", fmt.Errorf("The key '%s' cannot be found", keyUntilNow) } - valWithWrongType, ok := val.(map[string]interface{}) - if !ok { - return "", fmt.Errorf("Expected string key '%s' to refer to a dictionary; but we have map[%s][%s]", keyUntilNow, reflect.TypeOf(val).Key(), reflect.TypeOf(val).Elem()) - } currentData = make(map[string]interface{}) - for key, value := range valWithWrongType { - currentData[key] = value + // The 'if' here is to deal with key type discrepancy between YAML and + // JSON. With YAML, it is 'interface {}'; with JSON, it is 'string'. + if format == Json { + valWithWrongType, ok := val.(map[string]interface{}) + if !ok { + return "", fmt.Errorf("Expected string key '%s' to refer to a dictionary; but we have map[%s][%s]", keyUntilNow, reflect.TypeOf(val).Key(), reflect.TypeOf(val).Elem()) + } + for key, value := range valWithWrongType { + currentData[key] = value + } + } else { + valWithWrongType, ok := val.(map[interface {}]interface{}) + if !ok { + return "", fmt.Errorf("Expected key '%s' to refer to a dictionary; but we have map[%s][%s]", keyUntilNow, reflect.TypeOf(val).Key(), reflect.TypeOf(val).Elem()) + } + for key, value := range valWithWrongType { + currentData[key.(string)] = value + } } } @@ -284,7 +296,7 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error { case Binary, Dotenv, Ini: s.value = sourceFile.binary case Yaml, Json: - strVal, err := recurseSecretKey(sourceFile.data, s.Key) + strVal, err := recurseSecretKey(s.Format, sourceFile.data, s.Key) if err != nil { return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err) } @@ -444,7 +456,7 @@ func (app *appContext) validateSopsFile(s *secret, file *secretFile) error { file.firstSecret.Format, file.firstSecret.Name) } if app.checkMode != Manifest && (!(s.Format == Binary || s.Format == Dotenv || s.Format == Ini )) { - _, err := recurseSecretKey(file.keys, s.Key) + _, err := recurseSecretKey(s.Format, file.keys, s.Key) if err != nil { return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err) }