From 492adc12c7228d0876dede1a383b947f82324b87 Mon Sep 17 00:00:00 2001 From: Andre Azzolini Date: Thu, 20 Apr 2023 10:56:42 -0600 Subject: [PATCH 1/3] Add support for parsing secrets with visibility types - Include the new rawVisType and computedVisType properties - Convert raw and computed values to *string as they can now be null --- pkg/http/api.go | 26 ++++---------------------- pkg/models/api.go | 25 +++++++++++++++++++++---- pkg/models/parse.go | 44 +++++++++++++++++--------------------------- 3 files changed, 42 insertions(+), 53 deletions(-) diff --git a/pkg/http/api.go b/pkg/http/api.go index f495caec..88a7ddc2 100644 --- a/pkg/http/api.go +++ b/pkg/http/api.go @@ -247,22 +247,13 @@ func SetSecrets(host string, verifyTLS bool, apiKey string, project string, conf return nil, Error{Err: err, Message: "Unable to set secrets", Code: statusCode} } - var result map[string]interface{} + var result models.APISecretResponse err = json.Unmarshal(response, &result) if err != nil { return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} } - computed := map[string]models.ComputedSecret{} - for key, secret := range result["secrets"].(map[string]interface{}) { - val, ok := secret.(map[string]interface{}) - if !ok { - return nil, Error{Err: fmt.Errorf("Unexpected type mismatch for secret, expected map[string]interface{}, got %T", secret), Message: "Unable to parse API response", Code: statusCode} - } - computed[key] = models.ComputedSecret{Name: key, RawValue: val["raw"].(string), ComputedValue: val["computed"].(string)} - } - - return computed, Error{} + return models.ConvertAPIToComputedSecrets(result.Secrets), Error{} } // SetSecretNote for specified project and config @@ -346,22 +337,13 @@ func UploadSecrets(host string, verifyTLS bool, apiKey string, project string, c return nil, Error{Err: err, Message: "Unable to upload secrets", Code: statusCode} } - var result map[string]interface{} + var result models.APISecretResponse err = json.Unmarshal(response, &result) if err != nil { return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} } - computed := map[string]models.ComputedSecret{} - for key, secret := range result["secrets"].(map[string]interface{}) { - val, ok := secret.(map[string]interface{}) - if !ok { - return nil, Error{Err: fmt.Errorf("Unexpected type for secret, expected map[string]interface{}, got %T", secret), Message: "Unable to parse API response", Code: statusCode} - } - computed[key] = models.ComputedSecret{Name: key, RawValue: val["raw"].(string), ComputedValue: val["computed"].(string)} - } - - return computed, Error{} + return models.ConvertAPIToComputedSecrets(result.Secrets), Error{} } // GetWorkplaceSettings get specified workplace settings diff --git a/pkg/models/api.go b/pkg/models/api.go index d431262d..c413f10b 100644 --- a/pkg/models/api.go +++ b/pkg/models/api.go @@ -17,10 +17,12 @@ package models // ComputedSecret holds all info about a secret type ComputedSecret struct { - Name string `json:"name"` - RawValue string `json:"raw"` - ComputedValue string `json:"computed"` - Note string `json:"note"` + Name string `json:"name"` + RawValue *string `json:"raw"` + ComputedValue *string `json:"computed"` + RawVisibility string `json:"rawVisibility"` + ComputedVisibility string `json:"computedVisibility"` + Note string `json:"note"` } // SecretNote contains a secret and its note @@ -117,3 +119,18 @@ type ConfigServiceToken struct { Config string `json:"config"` Access string `json:"access"` } + +// APISecretResponse is the response the secrets endpoint returns +type APISecretResponse struct { + Success bool `json:"success"` + Secrets map[string]APISecret `json:"secrets"` +} + +// APISecret is the object the API returns for a given secret +type APISecret struct { + RawValue *string `json:"raw"` + ComputedValue *string `json:"computed"` + RawVisibility string `json:"rawVisibility"` + ComputedVisibility string `json:"computedVisibility"` + Note string `json:"note"` +} diff --git a/pkg/models/parse.go b/pkg/models/parse.go index bfea4b8d..f5f35ffe 100644 --- a/pkg/models/parse.go +++ b/pkg/models/parse.go @@ -231,40 +231,30 @@ func ParseActivityLog(log map[string]interface{}) ActivityLog { return parsedLog } +func ConvertAPIToComputedSecrets(apiSecrets map[string]APISecret) map[string]ComputedSecret { + computed := map[string]ComputedSecret{} + for key, secret := range apiSecrets { + computed[key] = ComputedSecret{ + Name: key, + RawValue: secret.RawValue, + ComputedValue: secret.ComputedValue, + RawVisibility: secret.RawVisibility, + ComputedVisibility: secret.ComputedVisibility, + Note: secret.Note, + } + } + return computed +} + // ParseSecrets parse secrets func ParseSecrets(response []byte) (map[string]ComputedSecret, error) { - var result map[string]interface{} + var result APISecretResponse err := json.Unmarshal(response, &result) if err != nil { return nil, err } - computed := map[string]ComputedSecret{} - secrets, ok := result["secrets"].(map[string]interface{}) - if !ok { - utils.LogDebug(fmt.Sprintf("Unexpected type mismatch for Secrets, expected map[string]interface{}, got %T", result["secrets"])) - utils.HandleError(errors.New("Unable to parse API response")) - } - for key, secret := range secrets { - computedSecret := ComputedSecret{Name: key} - val, ok := secret.(map[string]interface{}) - if !ok { - utils.LogDebug(fmt.Sprintf("Unexpected type mismatch for secret, expected map[string]interface{}, got %T", secret)) - utils.HandleError(errors.New("Unable to parse API response")) - } - if val["raw"] != nil { - computedSecret.RawValue = val["raw"].(string) - } - if val["computed"] != nil { - computedSecret.ComputedValue = val["computed"].(string) - } - if val["note"] != nil { - computedSecret.Note = val["note"].(string) - } - computed[key] = computedSecret - } - - return computed, nil + return ConvertAPIToComputedSecrets(result.Secrets), nil } // ParseConfigServiceToken parse config service token From 7c10c4b579047dffaa1d5b2dcc49a3d3fbe4b508 Mon Sep 17 00:00:00 2001 From: Andre Azzolini Date: Thu, 20 Apr 2023 10:58:46 -0600 Subject: [PATCH 2/3] Handle restricted secrets appropriately --- pkg/cmd/secrets.go | 6 ++++- pkg/printer/enclave.go | 58 +++++++++++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/pkg/cmd/secrets.go b/pkg/cmd/secrets.go index ca3d0dcf..2dcee53a 100644 --- a/pkg/cmd/secrets.go +++ b/pkg/cmd/secrets.go @@ -551,7 +551,11 @@ func substituteSecrets(cmd *cobra.Command, args []string) { secretsMap := map[string]string{} for _, secret := range secrets { - secretsMap[secret.Name] = secret.ComputedValue + if secret.ComputedValue != nil { + // By not providing a default value when ComputedValue is nil (e.g. it's a restricted secret), we default + // to the same behavior the substituter provides if the template file contains a secret that doesn't exist. + secretsMap[secret.Name] = *secret.ComputedValue + } } templateBody := controllers.ReadTemplateFile(args[0]) diff --git a/pkg/printer/enclave.go b/pkg/printer/enclave.go index a429f80b..043a739f 100644 --- a/pkg/printer/enclave.go +++ b/pkg/printer/enclave.go @@ -214,7 +214,11 @@ func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, vals := []string{} for _, name := range secretsToPrint { if secrets[name] != (models.ComputedSecret{}) { - vals = append(vals, secrets[name].ComputedValue) + if secrets[name].ComputedValue == nil { + utils.HandleError(fmt.Errorf("Unable to copy restricted value to clipboard")) + } else { + vals = append(vals, *secrets[name].ComputedValue) + } } } @@ -224,14 +228,28 @@ func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, } if jsonFlag { - secretsMap := map[string]map[string]string{} + secretsMap := map[string]map[string]interface{}{} for _, name := range secretsToPrint { if secrets[name] != (models.ComputedSecret{}) { - secretsMap[name] = map[string]string{"computed": secrets[name].ComputedValue, "note": secrets[name].Note} + secretsMap[name] = map[string]interface{}{ + "note": secrets[name].Note, + "computedVisibility": secrets[name].ComputedVisibility, + } + + if secrets[name].ComputedValue != nil { + secretsMap[name]["computed"] = *secrets[name].ComputedValue + } else { + secretsMap[name]["computed"] = nil + } + if raw { - secretsMap[name]["raw"] = secrets[name].RawValue + secretsMap[name]["rawVisibility"] = secrets[name].RawVisibility + if secrets[name].RawValue != nil { + secretsMap[name]["raw"] = *secrets[name].RawValue + } else { + secretsMap[name]["raw"] = nil + } } - secretsMap[name]["note"] = secrets[name].Note } } @@ -250,9 +268,17 @@ func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, vals := []string{} for _, secret := range matchedSecrets { if raw { - vals = append(vals, secret.RawValue) + if secret.RawValue != nil { + vals = append(vals, *secret.RawValue) + } else { + vals = append(vals, "") + } } else { - vals = append(vals, secret.ComputedValue) + if secret.ComputedValue != nil { + vals = append(vals, *secret.ComputedValue) + } else { + vals = append(vals, "") + } } } @@ -268,10 +294,24 @@ func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, var rows [][]string for _, secret := range matchedSecrets { - row := []string{secret.Name, secret.ComputedValue} + var computedValue string + if secret.ComputedValue != nil { + computedValue = *secret.ComputedValue + } else { + computedValue = "[RESTRICTED]" + } + + row := []string{secret.Name, computedValue} if raw { - row = append(row, secret.RawValue) + var rawValue string + if secret.RawValue != nil { + rawValue = *secret.RawValue + } else { + rawValue = "[RESTRICTED]" + } + row = append(row, rawValue) } + row = append(row, secret.Note) rows = append(rows, row) From 7fe88245ca38dddf897cacd750c7ac4027906335 Mon Sep 17 00:00:00 2001 From: Andre Azzolini Date: Thu, 20 Apr 2023 11:21:50 -0600 Subject: [PATCH 3/3] Add ability to print vistypes in the secrets table --- pkg/cmd/enclave_secrets.go | 2 ++ pkg/cmd/secrets.go | 14 +++++++++----- pkg/printer/enclave.go | 22 ++++++++++++++++++---- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pkg/cmd/enclave_secrets.go b/pkg/cmd/enclave_secrets.go index bceb0fb7..8810c2ae 100644 --- a/pkg/cmd/enclave_secrets.go +++ b/pkg/cmd/enclave_secrets.go @@ -97,6 +97,7 @@ func init() { enclaveSecretsCmd.Flags().StringP("project", "p", "", "enclave project (e.g. backend)") enclaveSecretsCmd.Flags().StringP("config", "c", "", "enclave config (e.g. dev)") enclaveSecretsCmd.Flags().Bool("raw", false, "print the raw secret value without processing variables") + enclaveSecretsCmd.Flags().Bool("visibility", false, "include secret visibility in table output") enclaveSecretsCmd.Flags().Bool("only-names", false, "only print the secret names; omit all values") enclaveSecretsGetCmd.Flags().StringP("project", "p", "", "enclave project (e.g. backend)") @@ -104,6 +105,7 @@ func init() { enclaveSecretsGetCmd.Flags().Bool("plain", false, "print values without formatting") enclaveSecretsGetCmd.Flags().Bool("copy", false, "copy the value(s) to your clipboard") enclaveSecretsGetCmd.Flags().Bool("raw", false, "print the raw secret value without processing variables") + enclaveSecretsGetCmd.Flags().Bool("visibility", false, "include secret visibility in table output") enclaveSecretsGetCmd.Flags().Bool("no-exit-on-missing-secret", false, "do not exit if unable to find a requested secret") enclaveSecretsCmd.AddCommand(enclaveSecretsGetCmd) diff --git a/pkg/cmd/secrets.go b/pkg/cmd/secrets.go index 2dcee53a..663bdbce 100644 --- a/pkg/cmd/secrets.go +++ b/pkg/cmd/secrets.go @@ -159,6 +159,7 @@ JSON Secret: "{\"logging\": \"info\"}"`, func secrets(cmd *cobra.Command, args []string) { jsonFlag := utils.OutputJSON raw := utils.GetBoolFlag(cmd, "raw") + visibility := utils.GetBoolFlag(cmd, "visibility") onlyNames := utils.GetBoolFlag(cmd, "only-names") localConfig := configuration.LocalConfig(cmd) @@ -181,7 +182,7 @@ func secrets(cmd *cobra.Command, args []string) { utils.HandleError(parseErr, "Unable to parse API response") } - printer.Secrets(secrets, []string{}, jsonFlag, false, raw, false) + printer.Secrets(secrets, []string{}, jsonFlag, false, raw, false, visibility) } } @@ -190,6 +191,7 @@ func getSecrets(cmd *cobra.Command, args []string) { plain := utils.GetBoolFlag(cmd, "plain") copy := utils.GetBoolFlag(cmd, "copy") raw := utils.GetBoolFlag(cmd, "raw") + visibility := utils.GetBoolFlag(cmd, "visibility") exitOnMissingSecret := !utils.GetBoolFlag(cmd, "no-exit-on-missing-secret") localConfig := configuration.LocalConfig(cmd) @@ -226,7 +228,7 @@ func getSecrets(cmd *cobra.Command, args []string) { } } - printer.Secrets(secrets, args, jsonFlag, plain, raw, copy) + printer.Secrets(secrets, args, jsonFlag, plain, raw, copy, visibility) } func setSecrets(cmd *cobra.Command, args []string) { @@ -331,7 +333,7 @@ func setSecrets(cmd *cobra.Command, args []string) { } if !utils.Silent { - printer.Secrets(response, keys, jsonFlag, false, raw, false) + printer.Secrets(response, keys, jsonFlag, false, raw, false, false) } } @@ -363,7 +365,7 @@ func uploadSecrets(cmd *cobra.Command, args []string) { } if !utils.Silent { - printer.Secrets(response, []string{}, jsonFlag, false, raw, false) + printer.Secrets(response, []string{}, jsonFlag, false, raw, false, false) } } @@ -387,7 +389,7 @@ func deleteSecrets(cmd *cobra.Command, args []string) { } if !utils.Silent { - printer.Secrets(response, []string{}, jsonFlag, false, raw, false) + printer.Secrets(response, []string{}, jsonFlag, false, raw, false, false) } } } @@ -590,6 +592,7 @@ func init() { secretsCmd.Flags().StringP("project", "p", "", "project (e.g. backend)") secretsCmd.Flags().StringP("config", "c", "", "config (e.g. dev)") secretsCmd.Flags().Bool("raw", false, "print the raw secret value without processing variables") + secretsCmd.Flags().Bool("visibility", false, "include secret visibility in table output") secretsCmd.Flags().Bool("only-names", false, "only print the secret names; omit all values") secretsGetCmd.Flags().StringP("project", "p", "", "project (e.g. backend)") @@ -597,6 +600,7 @@ func init() { secretsGetCmd.Flags().Bool("plain", false, "print values without formatting") secretsGetCmd.Flags().Bool("copy", false, "copy the value(s) to your clipboard") secretsGetCmd.Flags().Bool("raw", false, "print the raw secret value without processing variables") + secretsGetCmd.Flags().Bool("visibility", false, "include secret visibility in table output") secretsGetCmd.Flags().Bool("no-exit-on-missing-secret", false, "do not exit if unable to find a requested secret") secretsCmd.AddCommand(secretsGetCmd) diff --git a/pkg/printer/enclave.go b/pkg/printer/enclave.go index 043a739f..5767ee0c 100644 --- a/pkg/printer/enclave.go +++ b/pkg/printer/enclave.go @@ -202,7 +202,7 @@ func ProjectInfo(info models.ProjectInfo, jsonFlag bool) { } // Secrets print secrets -func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, jsonFlag bool, plain bool, raw bool, copy bool) { +func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, jsonFlag bool, plain bool, raw bool, copy bool, visibility bool) { if len(secretsToPrint) == 0 { for name := range secrets { secretsToPrint = append(secretsToPrint, name) @@ -286,9 +286,16 @@ func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, return } - headers := []string{"name", "value"} + headers := []string{"name"} + if visibility { + headers = append(headers, "visibility") + } + headers = append(headers, "value") if raw { - headers = append(headers, "raw") + if visibility { + headers = append(headers, "raw visibility") + } + headers = append(headers, "raw value") } headers = append(headers, "note") @@ -301,7 +308,11 @@ func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, computedValue = "[RESTRICTED]" } - row := []string{secret.Name, computedValue} + row := []string{secret.Name} + if visibility { + row = append(row, secret.ComputedVisibility) + } + row = append(row, computedValue) if raw { var rawValue string if secret.RawValue != nil { @@ -309,6 +320,9 @@ func Secrets(secrets map[string]models.ComputedSecret, secretsToPrint []string, } else { rawValue = "[RESTRICTED]" } + if visibility { + row = append(row, secret.RawVisibility) + } row = append(row, rawValue) }