2
votes

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
According to the documentation, the KmsKeyArn property on the a AWS::Lambda::Function is strictly for customer master key. That being said, why can you not use this kind of key? I was not able to understand why you need the default aws/lambda key.tyron
The keys created in KMS are customer master keys, and that's what I'm passing to KmsKeyArn. And I need to use this kind of key instead of the default key according to the following link, it says "You cannot use the default Lambda service key for encrypting sensitive information on the client side." docs.aws.amazon.com/lambda/latest/dg/tutorial-env_console.htmlDavid
So you want them to be Customer keys? This didn't make sense to me: "but the KMS ARN that I provided is showing up as a "Customer master key" rather than in the "AWS KMS key to encrypt in transit" section, which is where I need it to show up in order to encrypt the variables"tyron
@tyron I've re-phrased this to make it way more clear what I'm trying to do (including screenshot). Hopefully this helps.David
I think there might be a small confusion on the settings. You can define KmsKeyArn on CloudFormation, which implicates on the "AWS KMS key to encrypt at rest". This means the value is stored encrypted on AWS side, but they decrypt when showing to you on the Console. The data is still encrypted on the backend. If you want to mask the input, you can enable the "helper on transit". Which means they call KMS API to encrypt your string BEFORE storing. If you want to reproduce that, you have to encrypt manually your string before sending to CloudFormation on EnvironmentVariable.tyron

3 Answers

10
votes
  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

2
votes

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.

1
votes

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