Secure Properties With KMS And Parameter Store

8 Sep 2019 aws lambda kms golang parameter store

Amazon AWS offers an out of the box solution for secrets, called AWS Secrets Manager. If you’re hosting one or two secrets, then AWS Secrets Manager is a cost-effective solution, but when you hit three or more secrets there is a cheaper solution to store those secrest. This cheaper solution comes in the form of AWS Key Management Service (KMS) and AWS Systems Manager Parameter Store.

The pricing breakpoint comes at three secrest as AWS Secrets Manager has a $0.40/secret/month ($1.20/month for three secrets) while AWS Key Management Service (KMS) has a $1/month cost for customer generated secret and AWS Systems Manager Parameter Store is free for our usages.

Create Customer Master Key (CMK)

The first thing we’ll need to do is create a CMK inside KMS. We can follow this AWS guide on how to create the CMK or run the below command to create a CMK with default setings. The below command requires the kms:CreateKey permission for the users making the request.

aws kms create-key

Encrypt and put our first secret

Now that we have our CMK, we can encrypt and upload our secret into AWS Parameter Store. We’ll run the below command which requires the kms:Encrypt permission.

aws ssm put-parameter \
   --type String \
   --name '/PARAM/NAME' \
   --value $(aws kms encrypt \
              --output text \
              --query CiphertextBlob \
              --key-id <KEY_ID> \
              --plaintext "PLAIN TEXT HERE")

Fetch and decrypt out secret

We now have a CMK and an encrypted secret. The last item on our list is to decrypt that secret from parameter store. Since we’re going to be doing this inside an application, in my case in Go, I’ll provide the snippit in a copy/pastable format. The user that is going to be decrypting the secret will need the kms:Decrypt permission.

import (
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/kms"
)

func Decrypt(encrypted []byte, sess *session.Session) ([]byte, error) {
	kmsClient := kms.New(sess)
	decryptOutput, err := kmsClient.Decrypt(&kms.DecryptInput{
		CiphertextBlob:    encrypted,
	})

	if err != nil {
		return make([]byte, 0), err
	}

	return decryptOutput.Plaintext, nil
}

Bonus: Getting your secret into your AWS Lambda

The easiest way to get the secret into an AWS Lambda is to update your Lambdas SAM/Cloudformation deployment template.

Parameters:
  SomeSecretReference:
    Type: AWS::SSM::Parameter::Value<String>
    Default: '/PARAM/NAME'
Resources:
  ReceiveLambda:  
    Type: AWS::Serverless::Function
    Properties: 
      Environment:
        Variables:
          PARAM_NAME: !Ref SomeSecretReference

and then inside our AWS Lambda we can call the decrypt method like so:

secret, err := Decrypt([]byte(os.Getenv("PARAM_NAME")), session.Must(session.NewSession()))