Skip to content

Commit

Permalink
Merge pull request #258 from DopplerHQ/nic/name-transformers
Browse files Browse the repository at this point in the history
Add support for name transformers
  • Loading branch information
nmanoogian committed Dec 20, 2021
2 parents ae1ad87 + 0eaa9b8 commit 80a32a0
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 8 deletions.
23 changes: 18 additions & 5 deletions pkg/cmd/run.go
Expand Up @@ -102,7 +102,16 @@ doppler run --command "YOUR_COMMAND && YOUR_OTHER_COMMAND"`,
}
}

secrets := fetchSecrets(localConfig, enableCache, enableFallback, fallbackPath, legacyFallbackPath, metadataPath, fallbackReadonly, fallbackOnly, exitOnWriteFailure, passphrase)
nameTransformerString := cmd.Flag("name-transformer").Value.String()
var nameTransformer *models.SecretsNameTransformer
if nameTransformerString != "" {
nameTransformer = models.SecretsNameTransformerMap[nameTransformerString]
if nameTransformer == nil || !nameTransformer.EnvCompat {
utils.HandleError(fmt.Errorf("invalid name transformer. Valid transformers are %s", validEnvCompatNameTransformersList))
}
}

secrets := fetchSecrets(localConfig, enableCache, enableFallback, fallbackPath, legacyFallbackPath, metadataPath, fallbackReadonly, fallbackOnly, exitOnWriteFailure, passphrase, nameTransformer)

if preserveEnv {
utils.LogWarning("Ignoring Doppler secrets already defined in the environment due to --preserve-env flag")
Expand Down Expand Up @@ -241,22 +250,25 @@ var runCleanCmd = &cobra.Command{
}

// fetchSecrets fetches secrets, including all reading and writing of fallback files
func fetchSecrets(localConfig models.ScopedOptions, enableCache bool, enableFallback bool, fallbackPath string, legacyFallbackPath string, metadataPath string, fallbackReadonly bool, fallbackOnly bool, exitOnWriteFailure bool, passphrase string) map[string]string {
func fetchSecrets(localConfig models.ScopedOptions, enableCache bool, enableFallback bool, fallbackPath string, legacyFallbackPath string, metadataPath string, fallbackReadonly bool, fallbackOnly bool, exitOnWriteFailure bool, passphrase string, nameTransformer *models.SecretsNameTransformer) map[string]string {
if fallbackOnly {
if !enableFallback {
utils.HandleError(errors.New("Conflict: unable to specify --no-fallback with --fallback-only"))
}
if nameTransformer != nil {
utils.HandleError(errors.New("Conflict: unable to specify --name-transformer with --fallback-only"))
}
return readFallbackFile(fallbackPath, legacyFallbackPath, passphrase, false)
}

// this scenario likely isn't possible, but just to be safe, disable using cache when there's no metadata file
enableCache = enableCache && metadataPath != ""
enableCache = enableCache && nameTransformer == nil && metadataPath != ""
etag := ""
if enableCache {
etag = getCacheFileETag(metadataPath, fallbackPath)
}

statusCode, respHeaders, response, httpErr := http.DownloadSecrets(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, models.JSON, etag)
statusCode, respHeaders, response, httpErr := http.DownloadSecrets(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, models.JSON, nameTransformer, etag)
if !httpErr.IsNil() {
if enableFallback {
utils.Log("Unable to fetch secrets from the Doppler API")
Expand Down Expand Up @@ -293,7 +305,7 @@ func fetchSecrets(localConfig models.ScopedOptions, enableCache bool, enableFall
utils.HandleError(err, "Unable to parse API response")
}

writeFallbackFile := enableFallback && !fallbackReadonly
writeFallbackFile := enableFallback && !fallbackReadonly && nameTransformer == nil
if writeFallbackFile {
utils.LogDebug("Encrypting secrets")
encryptedResponse, err := crypto.Encrypt(passphrase, response)
Expand Down Expand Up @@ -541,6 +553,7 @@ func init() {
runCmd.Flags().StringP("config", "c", "", "config (e.g. dev)")
runCmd.Flags().String("command", "", "command to execute (e.g. \"echo hi\")")
runCmd.Flags().Bool("preserve-env", false, "ignore any Doppler secrets that are already defined in the environment. this has potential security implications, use at your own risk.")
runCmd.Flags().String("name-transformer", "", fmt.Sprintf("(BETA) output name transformer. one of %v", validEnvCompatNameTransformersList))
// fallback flags
runCmd.Flags().String("fallback", "", "path to the fallback file. encrypted secrets are written to this file after each successful fetch. secrets will be read from this file if subsequent connections are unsuccessful.")
// TODO rename this to 'fallback-passphrase' in CLI v4 (DPLR-435)
Expand Down
16 changes: 14 additions & 2 deletions pkg/cmd/secrets.go
Expand Up @@ -113,6 +113,8 @@ doppler secrets delete API_KEY CRYPTO_KEY`,
}

var validFormatList = strings.Join(models.SecretFormats, ", ")
var validNameTransformersList = strings.Join(models.SecretsNameTransformerTypes, ", ")
var validEnvCompatNameTransformersList = strings.Join(models.SecretsEnvCompatNameTransformerTypes, ", ")
var secretsDownloadCmd = &cobra.Command{
Use: "download <filepath>",
Short: "Download a config's secrets for later use",
Expand Down Expand Up @@ -417,6 +419,15 @@ func downloadSecrets(cmd *cobra.Command, args []string) {
}
}

nameTransformerString := cmd.Flag("name-transformer").Value.String()
var nameTransformer *models.SecretsNameTransformer
if nameTransformerString != "" {
nameTransformer = models.SecretsNameTransformerMap[nameTransformerString]
if nameTransformer == nil {
utils.HandleError(fmt.Errorf("invalid name transformer. Valid transformers are %s", validNameTransformersList))
}
}

fallbackPassphrase := getPassphrase(cmd, "fallback-passphrase", localConfig)
if fallbackPassphrase == "" {
utils.HandleError(errors.New("invalid fallback file passphrase"))
Expand All @@ -433,7 +444,7 @@ func downloadSecrets(cmd *cobra.Command, args []string) {
if enableCache {
metadataPath = controllers.MetadataFilePath(localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value)
}
secrets := fetchSecrets(localConfig, enableCache, enableFallback, fallbackPath, legacyFallbackPath, metadataPath, fallbackReadonly, fallbackOnly, exitOnWriteFailure, fallbackPassphrase)
secrets := fetchSecrets(localConfig, enableCache, enableFallback, fallbackPath, legacyFallbackPath, metadataPath, fallbackReadonly, fallbackOnly, exitOnWriteFailure, fallbackPassphrase, nameTransformer)

var err error
body, err = json.Marshal(secrets)
Expand All @@ -452,7 +463,7 @@ func downloadSecrets(cmd *cobra.Command, args []string) {
}

var apiError http.Error
_, _, body, apiError = http.DownloadSecrets(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, format, "")
_, _, body, apiError = http.DownloadSecrets(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, format, nameTransformer, "")
if !apiError.IsNil() {
utils.HandleError(apiError.Unwrap(), apiError.Message)
}
Expand Down Expand Up @@ -620,6 +631,7 @@ func init() {
secretsDownloadCmd.Flags().StringP("project", "p", "", "project (e.g. backend)")
secretsDownloadCmd.Flags().StringP("config", "c", "", "config (e.g. dev)")
secretsDownloadCmd.Flags().String("format", models.JSON.String(), fmt.Sprintf("output format. one of %s", validFormatList))
secretsDownloadCmd.Flags().String("name-transformer", "", fmt.Sprintf("(BETA) output name transformer. one of %v", validNameTransformersList))
secretsDownloadCmd.Flags().String("passphrase", "", "passphrase to use for encrypting the secrets file. the default passphrase is computed using your current configuration.")
secretsDownloadCmd.Flags().Bool("no-file", false, "print the response to stdout")
// fallback flags
Expand Down
5 changes: 4 additions & 1 deletion pkg/http/api.go
Expand Up @@ -136,11 +136,14 @@ func RevokeAuthToken(host string, verifyTLS bool, token string) (map[string]inte
}

// DownloadSecrets for specified project and config
func DownloadSecrets(host string, verifyTLS bool, apiKey string, project string, config string, format models.SecretsFormat, etag string) (int, http.Header, []byte, Error) {
func DownloadSecrets(host string, verifyTLS bool, apiKey string, project string, config string, format models.SecretsFormat, nameTransformer *models.SecretsNameTransformer, etag string) (int, http.Header, []byte, Error) {
var params []queryParam
params = append(params, queryParam{Key: "project", Value: project})
params = append(params, queryParam{Key: "config", Value: config})
params = append(params, queryParam{Key: "format", Value: format.String()})
if nameTransformer != nil {
params = append(params, queryParam{Key: "name_transformer", Value: nameTransformer.Type})
}

headers := apiKeyHeader(apiKey)
if etag != "" {
Expand Down
77 changes: 77 additions & 0 deletions pkg/models/secrets_name_transformer.go
@@ -0,0 +1,77 @@
/*
Copyright © 2021 Doppler <support@doppler.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package models

type SecretsNameTransformer struct {
Name string
Type string
EnvCompat bool
}

var UpperCamelTransformer = &SecretsNameTransformer{
Name: "Upper Camel",
Type: "upper-camel",
EnvCompat: true,
}
var CamelTransformer = &SecretsNameTransformer{
Name: "Camel",
Type: "camel",
EnvCompat: true,
}
var LowerSnakeTransformer = &SecretsNameTransformer{
Name: "Lower Snake",
Type: "lower-snake",
EnvCompat: true,
}
var TFVarTransformer = &SecretsNameTransformer{
Name: "TF Var",
Type: "tf-var",
EnvCompat: true,
}
var DotNETTransformer = &SecretsNameTransformer{
Name: ".NET",
Type: "dotnet",
EnvCompat: false,
}
var DotNETENVTransformer = &SecretsNameTransformer{
Name: ".NET (ENV)",
Type: "dotnet-env",
EnvCompat: true,
}

var SecretsNameTransformersList = []*SecretsNameTransformer{
UpperCamelTransformer,
CamelTransformer,
LowerSnakeTransformer,
TFVarTransformer,
DotNETTransformer,
DotNETENVTransformer,
}

var SecretsNameTransformerTypes []string
var SecretsEnvCompatNameTransformerTypes []string
var SecretsNameTransformerMap map[string]*SecretsNameTransformer

func init() {
SecretsNameTransformerMap = map[string]*SecretsNameTransformer{}
for _, transformer := range SecretsNameTransformersList {
SecretsNameTransformerTypes = append(SecretsNameTransformerTypes, transformer.Type)
SecretsNameTransformerMap[transformer.Type] = transformer
if transformer.EnvCompat {
SecretsEnvCompatNameTransformerTypes = append(SecretsEnvCompatNameTransformerTypes, transformer.Type)
}
}
}

0 comments on commit 80a32a0

Please sign in to comment.