Skip to content
Snippets Groups Projects
secrets_manager.go 5.82 KiB
Newer Older
Francé Wilke's avatar
Francé Wilke committed
package secrets_manager

import (
	"encoding/base64"
	credentials2 "github.com/aws/aws-sdk-go/aws/credentials"
Francé Wilke's avatar
Francé Wilke committed
	"os"

	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/struct_utils"
Francé Wilke's avatar
Francé Wilke committed

	"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"
	"github.com/aws/aws-secretsmanager-caching-go/secretcache"
)

type DatabaseCredentials struct {
	Username           string `json:"username"`
	Password           string `json:"password"`
	Engine             string `json:"engine"`
	Host               string `json:"host"`
	Port               int    `json:"port"`
	InstanceIdentifier string `json:"dbInstanceIdentifier"`
	ReadOnlyHost       string `json:"aurora_read_only_host"`
Francé Wilke's avatar
Francé Wilke committed
}

type S3UploadCredentials struct {
	AccessKeyID string `json:"accessKeyID"`
	SecretKey   string `json:"secretKey"`
Francé Wilke's avatar
Francé Wilke committed
var (
	secretCache, _      = secretcache.New()
	secretManagerRegion = "af-south-1"
)

var secretManagerSession *secretsmanager.SecretsManager

Francé Wilke's avatar
Francé Wilke committed
func GetDatabaseCredentials(secretID string, isDebug bool) (DatabaseCredentials, error) {
Johan de Klerk's avatar
Johan de Klerk committed
	secret, _ := GetSecret(secretID, isDebug)
Francé Wilke's avatar
Francé Wilke committed
	var credentials DatabaseCredentials
	err := struct_utils.UnmarshalJSON([]byte(secret), &credentials)
	if err != nil {
		return DatabaseCredentials{}, err
	}
	return credentials, nil
}

func GetS3UploadCredentials(secretID string, isDebug bool) (*credentials2.Credentials, error) {
	secret, _ := GetSecret(secretID, isDebug)
	var credentials S3UploadCredentials
	err := struct_utils.UnmarshalJSON([]byte(secret), &credentials)
	if err != nil {
		return nil, err
	}
	return credentials2.NewStaticCredentials(credentials.AccessKeyID, credentials.SecretKey, ""), nil
// getSecretManagerSession Instantiates a new Secrets Manager client session
func getSecretManagerSession(isDebug bool) (err error) {
Billy Griffiths's avatar
Billy Griffiths committed
	// If a session already exists, use it
	if secretManagerSession != nil {
		return nil
	}

	logs.Info("Creating a new Secrets Manager session")
	awsSession, err := session.NewSession()
Francé Wilke's avatar
Francé Wilke committed
	if err != nil {
Francé Wilke's avatar
Francé Wilke committed
	}

	// Get local config
	if isDebug && os.Getenv("ENVIRONMENT") != "" {
		awsAccessKey := os.Getenv("AWS_ACCESS_KEY_ID")
		if len(awsAccessKey) > 0 {
			logs.Info("Using access key %s", awsAccessKey)
		}
		awsSession, err = session.NewSessionWithOptions(session.Options{
			Config: aws.Config{
				Region:                        aws.String("af-south-1"),
				CredentialsChainVerboseErrors: aws.Bool(true),
			},
		})
		if err != nil {
Francé Wilke's avatar
Francé Wilke committed
	}

	// Create a Secrets Manager client session
	secretManagerSession = secretsmanager.New(awsSession, aws.NewConfig().WithRegion(secretManagerRegion))
}

// logError Logs any errors returned by the Secrets Manager client
func logError(err error) {
	if aerr, ok := err.(awserr.Error); ok {
		logs.Info(aerr.Code()+" %s", aerr.Error())
	} else {
		// Print the error, cast err to awserr.Error to get the Code and
		// Message from an error.
		logs.Info(err.Error())
	}
}

func GetSecret(secretID string, isDebug bool) (string, string) {
	// Check if we have the secret in cache
	cachedSecret, err := secretCache.GetSecretString(secretID)
	if err != nil {
		logs.Info("Failed to get secret key from cache")
	}
	if cachedSecret != "" {
		return cachedSecret, ""
	}
Francé Wilke's avatar
Francé Wilke committed

	// Create a Secrets Manager client
	err = getSecretManagerSession(isDebug)
	if err != nil {
		logs.Info("Could not create client: %+v", err)
		return "", ""
	}

	// Create a secret
Francé Wilke's avatar
Francé Wilke committed
	input := &secretsmanager.GetSecretValueInput{
		SecretId:     aws.String(string(secretID)),
		VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified
	}

	result, err := secretManagerSession.GetSecretValue(input)
Francé Wilke's avatar
Francé Wilke committed
	if err != nil {
Francé Wilke's avatar
Francé Wilke committed
		return "", ""
	}

	// 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 secretString, decodedBinarySecret string
	if result.SecretString != nil {
		secretString = *result.SecretString
	} else {
		decodedBinarySecretBytes := make([]byte, base64.StdEncoding.DecodedLen(len(result.SecretBinary)))
		length, err := base64.StdEncoding.Decode(decodedBinarySecretBytes, result.SecretBinary)
		if err != nil {
			logs.Info("Base64 Decode Error: %+v", err)
Francé Wilke's avatar
Francé Wilke committed
			return "", ""
		}
		decodedBinarySecret = string(decodedBinarySecretBytes[:length])
	}

	return secretString, decodedBinarySecret
}

// CreateSecret Creates a JSON marshaled "string secret" (can be expanded to cater for binary secrets should the need arise)
func CreateSecret(secretID string, secret any, isDebug bool) (awsSecretID string, err error) {
	// Create a Secrets Manager client
	err = getSecretManagerSession(isDebug)
	if err != nil {
		logs.Info("Could not create client: %+v", err)
		return "", err
	}

	// Create the secret - marshaling "any" into a JSON string
	secretStr, err := json.Marshal(secret)
	if err != nil {
		logs.Info("Could not marshal secret: %+v", err)
		return "", err
	}
	input := &secretsmanager.CreateSecretInput{
		Name:         aws.String(secretID),
		SecretString: aws.String(string(secretStr)),
	}

	result, err := secretManagerSession.CreateSecret(input)
	if err != nil {
		logError(err)
		return "", err
	}

	return aws.StringValue(result.Name), nil
}

func DeleteSecret(secretID string, forceWithoutRecovery bool, isDebug bool) error {
	// Create a Secrets Manager client
	err := getSecretManagerSession(isDebug)
	if err != nil {
		logs.Info("Could not create client: %+v", err)
		return err
	}

	// Delete the secret
	input := &secretsmanager.DeleteSecretInput{
		SecretId:                   aws.String(secretID),
		ForceDeleteWithoutRecovery: aws.Bool(forceWithoutRecovery),
	}

	_, err = secretManagerSession.DeleteSecret(input)
	if err != nil {
		logError(err)
		return err
	}

	return nil
}