package secrets_manager import ( "encoding/base64" "encoding/json" credentials2 "github.com/aws/aws-sdk-go/aws/credentials" "os" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/struct_utils" "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"` } type S3UploadCredentials struct { AccessKeyID string `json:"accessKeyID"` SecretKey string `json:"secretKey"` } var ( secretCache, _ = secretcache.New() secretManagerRegion = "af-south-1" ) var secretManagerSession *secretsmanager.SecretsManager func GetDatabaseCredentials(secretID string, isDebug bool) (DatabaseCredentials, error) { secret, _ := GetSecret(secretID, isDebug) 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) { // If a session already exists, use it if secretManagerSession != nil { return nil } logs.Info("Creating a new Secrets Manager session") awsSession, err := session.NewSession() if err != nil { return err } // 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 { return err } } // Create a Secrets Manager client session secretManagerSession = secretsmanager.New(awsSession, aws.NewConfig().WithRegion(secretManagerRegion)) return nil } // 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, "" } // Create a Secrets Manager client err = getSecretManagerSession(isDebug) if err != nil { logs.Info("Could not create client: %+v", err) return "", "" } // Create a secret input := &secretsmanager.GetSecretValueInput{ SecretId: aws.String(string(secretID)), VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified } result, err := secretManagerSession.GetSecretValue(input) if err != nil { logError(err) 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) 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 }