How to encrypt environment variables for Lambda via CLI?

Viewed 5085

enter image description here

When creating a Lambda function, it's not very hard to encrypt an environment variable via the GUI console. I just enter the key value pairs, then open the encryption helper and enter the ARN of my KMS key. This allows me to encrypt the value, so it's encrypted before it's sent to be stored, as shown in the image above.

What I'm trying to accomplish is this exact same thing, but assuming you're deploying that Lamba function not in the GUI console, but via a CloudFormation template, which is getting deployed in the CLI.

Here's how I'm specifying the relevant Parameters in the CloudFormation template:

"EnvironmentVariable" : {
      "Type" : "String",
      "Default" : "test",
      "Description" : "Environment Variable"
    },
    "KmsKeyArn" : {
      "Type" : "String",
      "Description" : "KMS Key ARN if environment variables are encrypted"
    },

Here's how I'm referencing those parameters in the Lambda resource, in the Resources section of the template:

"Environment" : {
   "Variables" : {
     "SomeVariable": {
       "Ref" : "EnvironmentVariable"
     }  
   }
 },
 "KmsKeyArn" : { "Ref" : "KmsKeyArn" },

And here's how I'm deploying this template in the CLI (with all my ARN and other values changed to protect privacy, but maintaining their structure):

aws cloudformation deploy --template-file lambda-template.json --stack-name "CLI-lambda-stack" --parameter-overrides S3BucketName="theBucket" S3FileLocation="lambda_function.zip" S3ObjectVersion="ZuB0eueEgh2yh5q00.DiykLNudujdsc5" DeadLetterArn="arn:aws:sns:us-west-2:526598937246:CloudFormationTests" EnvironmentVariable="testing" KmsKeyArn="arn:aws:kms:us-west-2:227866537246:key/b24e7c79-a14d-4a3e-b848-165115c86210" HandlerFunctionName="lambda_function.lambda_handler" MemorySize="128" Role="arn:aws:iam::507845137246:role/serverless-test-default-us-east-1-lambdaRole" FuncName="myCLILambda"

After running this in the CLI, I get no errors, but when I open the Lambda function in the console to inspect the results, I see something like this:

enter image description here

Where am I going wrong? Thanks for any insights.

3 Answers
  1. Create a KMS key if you don't already have one(AWS > IAM > Encryption Keys > Create New Key > Give alias name). Get the KeyId.
  2. Encrypt your variables via KMS. Example:

$ aws kms encrypt --key-id $KEY_ID --plaintext secretpassword --output text --query CiphertextBlob

AQICAHjZ+JlI8KKmiVc++NhnBcO0xX3LFAaCfsjH8Yjig3Yr2AFPIyKCp3XVOEDlbiTMWiejAAAAbDBqBgkqhkiG9w0BBwagXTBbAgEAMFYGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMhai9vkA2KdU5gd+qAgEQgCnWW4F3fb7pTwmA2ppskJhUl0dJGEXIE5oDCr3ZsH7TlN5X381juPg0LA==

Keep it in your CF template like this

"environment_variables": {
        "SECRET_DATA": "AQICAHjZ+JlI8KKmiVc++NhnBcO0xX3LFAaCfsjH8Yjig3Yr2AFPIyKCp3XVOEDlbiTMWiejAAAAbDBqBgkqhkiG9w0BBwagXTBbAgEAMFYGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMhai9vkA2KdU5gd+qAgEQgCnWW4F3fb7pTwmA2ppskJhUl0dJGEXIE5oDCr3ZsH7TlN5X381juPg0LA=="
       }

3.Decrypt inside your Lambda function

kms = boto3.client('kms')
response = kms.decrypt(CiphertextBlob=os.environ['SECRET_DATA'].decode('base64'))
secret_data = response['Plaintext']
secret_data

Or try this option: https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-about.html#sysman-paramstore-securestring

To summarize our comments:

There are 2 types of encryption: in transit and at rest. When you configure the KmsKeyArn property on CloudFormation, you are setting the "at rest" configuration on AWS Lambda, which makes AWS encrypt the information on their servers before storing. You never see that happening, neither you see the encrypted value.

The "in transit" encryption is the one you see on the console. As they say, the GUI has helpers to encrypt your text. This will make the text encrypted even for you, and they change it to *****. In order to use this variable in your function, you need to write code that will decrypt the value. An example is on How to use encrypted environment variables in AWS Lambda?.

To achieve the same results from the GUI using CLI, you need to manually encrypt your variables before calling the aws cloudformation deploy command.

AWS has changed how this works inside, so Paul's answer is now incomplete. Lambda now uses encryption context to decrypt the variables, so it needs to be used when encrypting the values. For example like this:

FUNCTION_NAME=lambda-function-name
PLAINTEXT=my-secret-variable

aws kms encrypt --key-id alias/the-alias \
  --plaintext fileb://<(echo -n ${PLAINTEXT}) \
  --encryption-context LambdaFunctionName=${FUNCTION_NAME} \
  --output text --query CiphertextBlob

"echo -n" is the only one that worked for me. Using cat file-path or specifying a path name directly produced a slightly different version with a trailing new line that failed the decryption.

I recommend encrypting a value with the aws console and ensuring that it can be decrypted. For example, put the console generated value in token-console.base64enc and try:

aws kms decrypt --region ap-southeast-2 \
  --ciphertext-blob fileb://<(cat token-console.base64enc | base64 --decode ) \
  --query Plaintext --output text --encryption-context \
  LambdaFunctionName=${FUNCTION_NAME} | base64 --decode
Related