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()))