From 4c4d97efd3d335df8f94885d669092c8130ff3e9 Mon Sep 17 00:00:00 2001 From: Nico Esteves Date: Fri, 23 Nov 2018 23:35:23 +0100 Subject: [PATCH] Added Secrets Manager support in kms-env --- VERSION | 2 +- kms/env/README.md | 75 +++++++++++++++++++++++++++++++++++------------ kms/env/main.go | 74 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 122 insertions(+), 29 deletions(-) diff --git a/VERSION b/VERSION index b3d91f9..509b0b6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.9.0 +5.10.0 diff --git a/kms/env/README.md b/kms/env/README.md index adea72b..df79cd3 100644 --- a/kms/env/README.md +++ b/kms/env/README.md @@ -1,25 +1,30 @@ # kms-env ``` -usage: kms_env [] ... +usage: kms-env [] ... -Decrypt environment variables encrypted with KMS or SSM. +Decrypt environment variables encrypted with KMS, SSM or Secret Manager. Flags: - --help Show context-sensitive help (also try --help-long and --help-man). - --assume-role-arn=ASSUME-ROLE-ARN - Role to assume - --assume-role-external-id=ASSUME-ROLE-EXTERNAL-ID - External ID of the role to assume - --assume-role-session-name=ASSUME-ROLE-SESSION-NAME - Role session name - --region=REGION AWS Region - --mfa-serial-number=MFA-SERIAL-NUMBER - MFA Serial Number - --mfa-token-code=MFA-TOKEN-CODE - MFA Token Code - --kms-prefix="KMS_" Prefix for the KMS environment variables - --ssm-prefix="SSM_" Prefix for the SSM environment variables + --help Show context-sensitive help (also try --help-long and --help-man). + --assume-role-arn=ASSUME-ROLE-ARN + Role to assume + --assume-role-external-id=ASSUME-ROLE-EXTERNAL-ID + External ID of the role to assume + --assume-role-session-name=ASSUME-ROLE-SESSION-NAME + Role session name + --region=REGION AWS Region + --mfa-serial-number=MFA-SERIAL-NUMBER + MFA Serial Number + --mfa-token-code=MFA-TOKEN-CODE + MFA Token Code + -v, --version Display the version + --kms-prefix="KMS_" Prefix for the KMS environment variables + --ssm-prefix="SSM_" Prefix for the SSM environment variables + --secrets-manager-prefix="SECRETS_MANAGER_" + Prefix for the secrets manager environment variables + --secrets-manager-version-stage="AWSCURRENT" + The version stage of secrets from secrets manager Args: Command to run, prefix with -- to pass args @@ -27,12 +32,15 @@ Args: ## Features -* Scans environment variables with the prefix `--kms-prefix` or `--ssm-prefix`, fetches and decrypts the values +* Scans environment variables with the prefix `--kms-prefix`, `--ssm-prefix` or `--secrets-manager-prefix`, fetches and decrypts the values then injects them into the environment of the sub command to run. * KMS values should be base64 encoded in the value of the variable * SSM values should be the path to the parameter store parameter. If the path ends in `/*` it will fetch the values - under that path (non-recursively) and prefix them with the original env var. If the name is prefix with an extra _ + under that path (non-recursively) and prefix them with the original env var. If the name is prefix with an extra `_`. no prefix is used +* Secret manager values should be the name of the secret. It supports JSON encoded values in either SecretString or SecretBinary and will fetch + the AWSCURRENT version by default (override with `--secrets-manager-version-stage`). If JSON keys are upper cased and prefixed with the name of the + environment variable excluding the prefix. To not include the prefix, use an extra `_` after the prefix. ## Examples @@ -55,7 +63,7 @@ kms-env program `program` will be called with its environment set to the parent process environment with the additional env var `B` with its value set to the value of the parameter under `/path/to/value`. If the parameter was encrypted with KMS, it is automatically -decrypted. +decrypted. ### SSM wildcard @@ -88,3 +96,32 @@ Will have * `FLIP` If multiple variables have a double `_` they all get merged. (Note: there is no guarantee of the order in which they are processed) + +### Secrets Manager with prefix + +Assuming the secret `name/of/secret` exists +``` +{"foo": 123, "bar": "test"} +``` + +``` +export SECRETS_MANAGER_ABC=name/of/secret +kms-env program +``` + +Will have +* `ABC_FOO=123` +* `ABC_BAR=test` + +### Secrets Manager without prefix + +Add an extra `_` in the environment variable name + +``` +export SECRETS_MANAGER__ABC=name/of/secret +kms-env program +``` + +Will have +* `FOO=123` +* `BAR=test diff --git a/kms/env/main.go b/kms/env/main.go index 3945d3f..2fbdb49 100644 --- a/kms/env/main.go +++ b/kms/env/main.go @@ -2,7 +2,9 @@ package main import ( "bytes" + "encoding/base64" "encoding/gob" + "encoding/json" "fmt" "os" "os/exec" @@ -10,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/kms" + "github.com/aws/aws-sdk-go/service/secretsmanager" "github.com/aws/aws-sdk-go/service/ssm" "github.com/hamstah/awstools/common" "golang.org/x/crypto/nacl/secretbox" @@ -17,22 +20,25 @@ import ( ) var ( - flags = common.KingpinSessionFlags() - infoFlags = common.KingpinInfoFlags() - command = kingpin.Arg("command", "Command to run, prefix with -- to pass args").Required().Strings() - kmsPrefix = kingpin.Flag("kms-prefix", "Prefix for the KMS environment variables").Default("KMS_").String() - ssmPrefix = kingpin.Flag("ssm-prefix", "Prefix for the SSM environment variables").Default("SSM_").String() + flags = common.KingpinSessionFlags() + infoFlags = common.KingpinInfoFlags() + command = kingpin.Arg("command", "Command to run, prefix with -- to pass args").Required().Strings() + kmsPrefix = kingpin.Flag("kms-prefix", "Prefix for the KMS environment variables").Default("KMS_").String() + ssmPrefix = kingpin.Flag("ssm-prefix", "Prefix for the SSM environment variables").Default("SSM_").String() + secretsManagerPrefix = kingpin.Flag("secrets-manager-prefix", "Prefix for the secrets manager environment variables").Default("SECRETS_MANAGER_").String() + secretsManagerVersionStage = kingpin.Flag("secrets-manager-version-stage", "The version stage of secrets from secrets manager").Default("AWSCURRENT").String() ) func main() { - kingpin.CommandLine.Name = "kms_env" - kingpin.CommandLine.Help = "Decrypt environment variables encrypted with KMS or SSM." + kingpin.CommandLine.Name = "kms-env" + kingpin.CommandLine.Help = "Decrypt environment variables encrypted with KMS, SSM or Secret Manager." kingpin.Parse() common.HandleInfoFlags(infoFlags) session, conf := common.OpenSession(flags) kmsClient := kms.New(session, conf) ssmClient := ssm.New(session, conf) + secretsManagerClient := secretsmanager.New(session, conf) env := os.Environ() var pEnv []string @@ -42,7 +48,7 @@ func main() { continue } - result, err := handleEnvVar(kmsClient, ssmClient, parts[0], parts[1]) + result, err := handleEnvVar(kmsClient, ssmClient, secretsManagerClient, parts[0], parts[1]) common.FatalOnError(err) for newKey, newValue := range result { pEnv = append(pEnv, fmt.Sprintf("%s=%s", newKey, newValue)) @@ -57,7 +63,7 @@ func main() { p.Run() } -func handleEnvVar(kmsClient *kms.KMS, ssmClient *ssm.SSM, key, value string) (map[string]string, error) { +func handleEnvVar(kmsClient *kms.KMS, ssmClient *ssm.SSM, secretsManagerClient *secretsmanager.SecretsManager, key, value string) (map[string]string, error) { if strings.HasPrefix(key, *kmsPrefix) { newValue, err := kmsDecrypt(kmsClient, value) if err != nil { @@ -79,6 +85,12 @@ func handleEnvVar(kmsClient *kms.KMS, ssmClient *ssm.SSM, key, value string) (ma } return map[string]string{key[len(*ssmPrefix):]: newValue}, nil } + } else if strings.HasPrefix(key, *secretsManagerPrefix) { + prefix := key[len(*secretsManagerPrefix):] + if strings.HasPrefix(prefix, "_") { + prefix = "" + } + return fetchSecret(secretsManagerClient, value, prefix) } return map[string]string{key: value}, nil @@ -107,6 +119,20 @@ func getParametersByPath(client *ssm.SSM, path string, prefix string) (map[strin return result, nil } +func ConvertMap(source map[string]string, prefix string) map[string]string { + res := make(map[string]string, len(source)) + for key, value := range source { + var newKey string + if prefix == "" { + newKey = strings.ToUpper(key) + } else { + newKey = fmt.Sprintf("%s_%s", prefix, strings.ToUpper(key)) + } + res[newKey] = value + } + return res +} + const ( keyLength = 32 nonceLength = 24 @@ -152,3 +178,33 @@ func kmsDecrypt(kmsClient *kms.KMS, ciphertext string) (string, error) { } return string(plaintext), nil } + +func fetchSecret(secretsManagerClient *secretsmanager.SecretsManager, secretName, prefix string) (map[string]string, error) { + result, err := secretsManagerClient.GetSecretValue(&secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretName), + VersionStage: secretsManagerVersionStage, + }) + if err != nil { + return nil, err + } + + var content []byte + if result.SecretString != nil { + content = []byte(*result.SecretString) + } else { + decodedBinarySecretBytes := make([]byte, base64.StdEncoding.DecodedLen(len(result.SecretBinary))) + len, err := base64.StdEncoding.Decode(decodedBinarySecretBytes, result.SecretBinary) + if err != nil { + return nil, err + } + content = decodedBinarySecretBytes[:len] + } + + res := make(map[string]string) + err = json.Unmarshal(content, &res) + if err != nil { + return nil, err + } + + return ConvertMap(res, prefix), nil +}