From 600e87f4c03e8f8193a7cb5bb9ec244946babfca Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 2 Mar 2020 14:37:39 +0100 Subject: [PATCH] Initial commit --- README.md | 36 ++++++++++ main.go | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 README.md create mode 100644 main.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd728ff --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# AWS Secrets Manager Loader + +Simple go application to load secrets from AWS Secrets Manager and output them to stdout + +## Build + +``` +go build -o aws_sm_loader main.go +``` + +## Usage + +The application expects the env variable `AWS_REGION` to be set. +To filter the secrets you want to retrieve use AWS tags. Set tags as env variables before running the application with prefix `SM_TAG_`. + +To get all secrets tagged with FOO=bar use +``` +export SM_TAG_FOO=bar +./aws_sm_loader +``` + +The secrets matching **all** tags will be printed to stdout in the following format +``` +export FOO=bar +export FOO2=bar2 +... +``` + +You can the use eval to export the env variables, for example in dumb-init entrypoint +``` +ENTRYPOINT ["/usr/bin/dumb-init", "--"] +CMD ["bash", "-c", "eval $(./aws_sm_loader) && exec printenv"] +``` + + + diff --git a/main.go b/main.go new file mode 100644 index 0000000..a338b47 --- /dev/null +++ b/main.go @@ -0,0 +1,191 @@ +package main + +// Use this code snippet in your app. +// If you need more information about configurations or implementing the sample code, visit the AWS docs: +// https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/setting-up.html + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/secretsmanager" +) + +var ( + region string +) + +type Secret struct { + Key string + Value string +} + +func getSecret(secretName string) *string { + + //Create a Secrets Manager client + svc := secretsmanager.New(session.New(), + aws.NewConfig().WithRegion(region)) + input := &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretName), + VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified + } + + // In this sample we only handle the specific exceptions for the 'GetSecretValue' API. + // See https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html + + result, err := svc.GetSecretValue(input) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case secretsmanager.ErrCodeDecryptionFailure: + // Secrets Manager can't decrypt the protected secret text using the provided KMS key. + fmt.Println(secretsmanager.ErrCodeDecryptionFailure, aerr.Error()) + + case secretsmanager.ErrCodeInternalServiceError: + // An error occurred on the server side. + fmt.Println(secretsmanager.ErrCodeInternalServiceError, aerr.Error()) + + case secretsmanager.ErrCodeInvalidParameterException: + // You provided an invalid value for a parameter. + fmt.Println(secretsmanager.ErrCodeInvalidParameterException, aerr.Error()) + + case secretsmanager.ErrCodeInvalidRequestException: + // You provided a parameter value that is not valid for the current state of the resource. + fmt.Println(secretsmanager.ErrCodeInvalidRequestException, aerr.Error()) + + case secretsmanager.ErrCodeResourceNotFoundException: + // We can't find the resource that you asked for. + fmt.Println(secretsmanager.ErrCodeResourceNotFoundException, aerr.Error()) + } + } else { + // Print the error, cast err to awserr.Error to get the Code and + // Message from an error. + fmt.Println(err.Error()) + } + fmt.Println(err.Error()) + return nil + } + + // Decrypts secret using the associated KMS CMK. + // Depending on whether the secret is a string or binary, one of these fields will be populated. + var decodedBinarySecret string + if result.SecretString != nil { + return result.SecretString + } else { + decodedBinarySecretBytes := make([]byte, base64.StdEncoding.DecodedLen(len(result.SecretBinary))) + len, err := base64.StdEncoding.Decode(decodedBinarySecretBytes, result.SecretBinary) + if err != nil { + fmt.Println("Base64 Decode Error:", err) + return nil + } + decodedBinarySecret = string(decodedBinarySecretBytes[:len]) + return &decodedBinarySecret + } +} + +func listAllSecrets() *secretsmanager.ListSecretsOutput { + svc := secretsmanager.New(session.New(), + aws.NewConfig().WithRegion(region)) + input := &secretsmanager.ListSecretsInput{} + + result, err := svc.ListSecrets(input) + if err != nil { + fmt.Println(err.Error()) + } + return result +} + +func filterSecrets(targetTags map[string]string) []string { + allSecrets := listAllSecrets() + var filteredSecrets []string + for _, secret := range allSecrets.SecretList { + + // If secret has no tags, skip it + if len(secret.Tags) == 0 { + continue + } + + // Convert tags on resource into map + resourceTags := make(map[string]string) + for _, tag := range secret.Tags { + resourceTags[*tag.Key] = *tag.Value + } + + // Check if resource has all required tags specified in env + hasAllTags := true + for key, value := range targetTags { + if resourceTags[key] != value { + hasAllTags = false + break + } + } + + if hasAllTags { + filteredSecrets = append(filteredSecrets, *secret.Name) + } + } + + return filteredSecrets +} + +func filterEnvVars(targetPrefix string) map[string]string { + + var result map[string]string + + allVars := os.Environ() + result = make(map[string]string) + for _, env := range allVars { + + if strings.HasPrefix(env, targetPrefix) { + trimmed := strings.TrimPrefix(env, targetPrefix) + pair := strings.SplitN(trimmed, "=", 2) + result[pair[0]] = pair[1] + } + } + + return result +} + +func parseSecrets(secretsNames []string) []string { + var secrets []string + + for _, secret := range secretsNames { + + var parsedSecret map[string]string + err := json.Unmarshal([]byte(*getSecret(secret)), &parsedSecret) + if err != nil { + fmt.Println(err) + } + + for key, value := range parsedSecret { + secrets = append(secrets, "export "+key+"='"+value+"'") + } + } + + return secrets +} + +func main() { + + region = os.Getenv("AWS_REGION") + sm_tags := filterEnvVars("SM_TAG_") + + if len(sm_tags) == 0 { + err := errors.New("No tags for secrets filtering specified") + panic(err) + } + + filteredSecretsNames := filterSecrets(sm_tags) + parsedSecrets := parseSecrets(filteredSecretsNames) + + for _, s := range parsedSecrets { + fmt.Println(s) + } +}