Skip to content

Commit

Permalink
Merge pull request #383 from DopplerHQ/andre/vistypes
Browse files Browse the repository at this point in the history
Add support for secret visibility types
  • Loading branch information
apazzolini authored May 3, 2023
2 parents c3b044e + 7fe8824 commit cbeb2fe
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 71 deletions.
2 changes: 2 additions & 0 deletions pkg/cmd/enclave_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func init() {
enclaveSecretsCmd.Flags().StringP("config", "c", "", "enclave config (e.g. dev)")
enclaveSecretsCmd.RegisterFlagCompletionFunc("config", configNamesValidArgs)
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)")
Expand All @@ -108,6 +109,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)

Expand Down
20 changes: 14 additions & 6 deletions pkg/cmd/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
}
}

Expand All @@ -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)

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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)
}
}
}
Expand Down Expand Up @@ -551,7 +553,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])
Expand Down Expand Up @@ -588,6 +594,7 @@ func init() {
secretsCmd.Flags().StringP("config", "c", "", "config (e.g. dev)")
secretsCmd.RegisterFlagCompletionFunc("config", configNamesValidArgs)
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)")
Expand All @@ -597,6 +604,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)

Expand Down
26 changes: 4 additions & 22 deletions pkg/http/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
25 changes: 21 additions & 4 deletions pkg/models/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"`
}
44 changes: 17 additions & 27 deletions pkg/models/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
78 changes: 66 additions & 12 deletions pkg/printer/enclave.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
}
}

Expand All @@ -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
}
}

Expand All @@ -250,28 +268,64 @@ 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, "")
}
}
}

fmt.Println(strings.Join(vals, "\n"))
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")

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}
if visibility {
row = append(row, secret.ComputedVisibility)
}
row = append(row, computedValue)
if raw {
row = append(row, secret.RawValue)
var rawValue string
if secret.RawValue != nil {
rawValue = *secret.RawValue
} else {
rawValue = "[RESTRICTED]"
}
if visibility {
row = append(row, secret.RawVisibility)
}
row = append(row, rawValue)
}

row = append(row, secret.Note)

rows = append(rows, row)
Expand Down

0 comments on commit cbeb2fe

Please sign in to comment.