-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 600e87f
Showing
2 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] | ||
``` | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |