Skip to content

Commit

Permalink
Added Secrets Manager support in kms-env
Browse files Browse the repository at this point in the history
  • Loading branch information
hamstah committed Nov 23, 2018
1 parent 41ce49a commit 4c4d97e
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 29 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.9.0
5.10.0
75 changes: 56 additions & 19 deletions kms/env/README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,46 @@
# kms-env

```
usage: kms_env [<flags>] <command>...
usage: kms-env [<flags>] <command>...
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> Command to run, prefix with -- to pass 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

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

Expand Down Expand Up @@ -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
74 changes: 65 additions & 9 deletions kms/env/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,43 @@ package main

import (
"bytes"
"encoding/base64"
"encoding/gob"
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"

"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"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)

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

0 comments on commit 4c4d97e

Please sign in to comment.