Skip to content

Commit

Permalink
feat(sm): add initial actions for AWS secrets manager
Browse files Browse the repository at this point in the history
Signed-off-by: Derek Smith <derek@clokwork.net>
  • Loading branch information
clok committed Jun 17, 2021
1 parent 041d4bf commit 91288e1
Show file tree
Hide file tree
Showing 11 changed files with 1,234 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .gitignore
@@ -1,3 +1,13 @@
# Created by .ignore support plugin (hsz.mobi)
### Example user template template
### Example user template

# IntelliJ project files
.idea
*.iml
out
gen
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
Expand All @@ -13,3 +23,9 @@

# Dependency directories (remove the comment below to include it)
# vendor/

build/
.DS_Store
bin/
dist/
sm
304 changes: 304 additions & 0 deletions aws/sm.go
@@ -0,0 +1,304 @@
package sm

import (
"encoding/json"
"fmt"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/secretsmanager"
as "github.com/clok/awssession"
)

func getValueByKey(keyName string, secretBytes []byte) (secret []byte, err error) {
var secrets map[string]interface{}
var secretValue string

if err := json.Unmarshal(secretBytes, &secrets); err != nil {
return nil, err
}

secretValue = fmt.Sprint(secrets[keyName])

return []byte(secretValue), nil
}

// RetrieveSecret will pull the AWS Secrets Manager value and parse out the specific value needed.
//
// NOTE: Refactor of https://github.com/cyberark/summon-aws-secrets/blob/master/main.go
// This was needed to have the command return the byte stream rather than have it write
// to STDOUT
func RetrieveSecret(variableName string) (secretBytes []byte, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

// Check if key has been specified
arguments := strings.SplitN(variableName, "#", 2)

secretName := arguments[0]
var keyName string

if len(arguments) > 1 {
keyName = arguments[1]
}

exists, err := CheckIfSecretExists(secretName)
if err != nil {
return nil, err
}
if !exists {
return nil, fmt.Errorf("'%s' secret does not exist", secretName)
}

// Get secret value
req, resp := svc.GetSecretValueRequest(&secretsmanager.GetSecretValueInput{
SecretId: aws.String(secretName),
})

err = req.Send()
if err != nil {
return nil, err
}

if resp.SecretString != nil {
secretBytes = []byte(*resp.SecretString)
} else {
secretBytes = resp.SecretBinary
}

if keyName != "" {
secretBytes, err = getValueByKey(keyName, secretBytes)
if err != nil {
return nil, err
}
}

return
}

// ListSecrets will retrieval ALL secrets via pagination of 100 per page. It will
// return once all pages have been processed.
func ListSecrets() (secrets []secretsmanager.SecretListEntry, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

// Get all secret names
err = svc.ListSecretsPages(&secretsmanager.ListSecretsInput{
MaxResults: aws.Int64(100),
},
func(page *secretsmanager.ListSecretsOutput, lastPage bool) bool {
for _, v := range page.SecretList {
secrets = append(secrets, *v)
}
return !lastPage
})
if err != nil {
return nil, err
}

return
}

// GetSecret will retrieve a specific secret by Name (id)
func GetSecret(id string) (secret *secretsmanager.GetSecretValueOutput, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

secret, err = svc.GetSecretValue(&secretsmanager.GetSecretValueInput{
SecretId: aws.String(id),
})

if err != nil {
return nil, err
}

return
}

// DeleteSecret will retrieve a specific secret by Name (id)
func DeleteSecret(id string, force bool) (secret *secretsmanager.DeleteSecretOutput, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

secret, err = svc.DeleteSecret(&secretsmanager.DeleteSecretInput{
SecretId: aws.String(id),
ForceDeleteWithoutRecovery: aws.Bool(force),
})

if err != nil {
return nil, err
}

return
}

// PutSecretString will put an updated SecretString value to a specific secret by Name (id)
func PutSecretString(id string, data string) (secret *secretsmanager.PutSecretValueOutput, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

secret, err = svc.PutSecretValue(&secretsmanager.PutSecretValueInput{
SecretString: aws.String(data),
SecretId: aws.String(id),
})

if err != nil {
return nil, err
}

return
}

// PutSecretBinary will put an updated SecretBinary value to a specific secret by Name (id)
func PutSecretBinary(id string, data []byte) (secret *secretsmanager.PutSecretValueOutput, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

secret, err = svc.PutSecretValue(&secretsmanager.PutSecretValueInput{
SecretBinary: data,
SecretId: aws.String(id),
})

if err != nil {
return nil, err
}

return
}

// CreateSecretString will create a new SecretString value to a specific secret by Name (id)
func CreateSecretString(id string, data string, description string, tagsCSV string) (secret *secretsmanager.CreateSecretOutput, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

input := secretsmanager.CreateSecretInput{
SecretString: aws.String(data),
Name: aws.String(id),
}

if description != "" {
input.Description = aws.String(description)
}

if tagsCSV != "" {
var tags []*secretsmanager.Tag
for _, kv := range strings.Split(tagsCSV, ",") {
parts := strings.SplitN(kv, "=", 2)
tags = append(tags, &secretsmanager.Tag{
Key: aws.String(parts[0]),
Value: aws.String(parts[1]),
})
}
input.Tags = tags
}

secret, err = svc.CreateSecret(&input)

if err != nil {
return nil, err
}

return
}

// CreateSecretBinary will create a new SecretBinary value to a specific secret by Name (id)
func CreateSecretBinary(id string, data []byte, description string, tagsCSV string) (secret *secretsmanager.CreateSecretOutput, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

input := secretsmanager.CreateSecretInput{
SecretBinary: data,
Name: aws.String(id),
}

if description != "" {
input.Description = aws.String(description)
}

if tagsCSV != "" {
var tags []*secretsmanager.Tag
for _, kv := range strings.Split(tagsCSV, ",") {
parts := strings.SplitN(kv, "=", 2)
tags = append(tags, &secretsmanager.Tag{
Key: aws.String(parts[0]),
Value: aws.String(parts[1]),
})
}
input.Tags = tags
}

secret, err = svc.CreateSecret(&input)

if err != nil {
return nil, err
}

return
}

// DescribeSecret retrieves the describe data for a specific secret by Name (id)
func DescribeSecret(id string) (secret *secretsmanager.DescribeSecretOutput, err error) {
sess, err := as.New()
if err != nil {
return nil, err
}
svc := secretsmanager.New(sess)

secret, err = svc.DescribeSecret(&secretsmanager.DescribeSecretInput{
SecretId: aws.String(id),
})
if err != nil {
return nil, err
}

return
}

// CheckIfSecretExists determines if the input secret ID already exists in AWS Secrets Manager
func CheckIfSecretExists(id string) (bool, error) {
sess, err := as.New()
if err != nil {
return true, err
}
svc := secretsmanager.New(sess)

_, err = svc.DescribeSecret(&secretsmanager.DescribeSecretInput{
SecretId: aws.String(id),
})

if err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == secretsmanager.ErrCodeResourceNotFoundException {
return false, nil
}
}
return true, err
}

return true, nil
}
20 changes: 20 additions & 0 deletions cmd/logger.go
@@ -0,0 +1,20 @@
package cmd

import (
"fmt"
"os"

a "github.com/logrusorgru/aurora/v3"
)

func PrintWarn(s string) {
_, _ = fmt.Fprintln(os.Stderr, a.Sprintf(a.Red("✖ %s"), s))
}

func PrintSuccess(s string) {
_, _ = fmt.Fprintln(os.Stderr, a.Sprintf(a.Green("✔ %s"), s))
}

func PrintInfo(s string) {
_, _ = fmt.Fprintln(os.Stderr, a.Sprintf(a.Gray(14, "➜ %s"), s))
}

0 comments on commit 91288e1

Please sign in to comment.