├── overview.png ├── cdk ├── jest.config.js ├── package.json ├── tsconfig.json ├── cdk.json ├── test │ └── cdk.test.ts ├── bin │ └── cdk.ts └── lib │ └── cdk-stack.ts ├── .npmignore ├── test ├── python-test.py ├── second-example-layer └── lambda_function.py ├── CODE_OF_CONDUCT.md ├── .gitignore ├── src ├── go.mod ├── get-secrets-layer ├── go.sum └── go-retrieve-secret.go ├── LICENSE ├── CONTRIBUTING.md └── README.md /overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lambda-environmental-variables-from-aws-secrets-manager/HEAD/overview.png -------------------------------------------------------------------------------- /cdk/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | 6 | *.ts 7 | !*.d.ts 8 | 9 | # CDK asset staging directory 10 | .cdk.staging 11 | cdk.out 12 | -------------------------------------------------------------------------------- /test/python-test.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | import os 6 | 7 | # printing environment variables as a test to ensure 8 | # that the retrieved Secret is stored within the 9 | # environment 10 | print(os.environ) -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | 6 | # Ignore output and generated content 7 | *.js 8 | !jest.config.js 9 | *.d.ts 10 | node_modules 11 | *.zip 12 | out/ 13 | .cdk.staging 14 | cdk.out 15 | 16 | # Ignore for VS Code and Mac 17 | .vscode 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /test/second-example-layer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # SPDX-License-Identifier: MIT-0 6 | # 7 | # Example second layer used for testing purposes 8 | # 9 | 10 | args=("$@") 11 | 12 | export SECOND_LAYER_EXECUTE="true" 13 | 14 | echo "Args from second layer = ${args[@]}" 15 | 16 | exec ${args[@]} 17 | -------------------------------------------------------------------------------- /src/go.mod: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0 4 | // 5 | module go-retrieve-secret 6 | 7 | go 1.15 8 | 9 | require ( 10 | github.com/aws/aws-sdk-go v1.40.35 // indirect 11 | github.com/aws/aws-sdk-go-v2 v1.9.0 12 | github.com/aws/aws-sdk-go-v2/config v1.7.0 // indirect 13 | github.com/aws/aws-sdk-go-v2/credentials v1.4.0 // indirect 14 | github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.6.0 // indirect 15 | github.com/aws/aws-sdk-go-v2/service/ssm v1.10.0 // indirect 16 | github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /cdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk", 3 | "version": "0.1.0", 4 | "bin": { 5 | "aws-lambda-environmental-variables-from-aws-secrets-manager": "bin/cdk.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^26.0.10", 15 | "@types/node": "10.17.27", 16 | "aws-cdk": "2.15.0", 17 | "jest": "^26.6.3", 18 | "ts-jest": "^26.2.0", 19 | "ts-node": "^9.0.0", 20 | "typescript": "~3.9.7" 21 | }, 22 | "dependencies": { 23 | "aws-cdk-lib": "2.15.0", 24 | "constructs": "^10.0.0", 25 | "source-map-support": "^0.5.16" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 25 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 26 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 27 | "@aws-cdk/core:target-partitions": [ 28 | "aws", 29 | "aws-cn" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cdk/test/cdk.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0 4 | // 5 | // Test code for CDK 6 | // 7 | import * as cdk from 'aws-cdk-lib'; 8 | import { Template } from 'aws-cdk-lib/assertions'; 9 | import * as Cdk from '../lib/cdk-stack'; 10 | 11 | // This test will ensure that the necessary constructs are created in the template 12 | test('Lambda Function Created', () => { 13 | const app = new cdk.App(); 14 | // WHEN 15 | const stack = new Cdk.CdkStack(app, 'MyTestStack'); 16 | // THEN 17 | const template = Template.fromStack(stack); 18 | 19 | template.hasResourceProperties('AWS::Lambda::Function', { 20 | "Runtime": "python3.9" 21 | }); 22 | }); 23 | 24 | // This test will ensure that the necessary constructs are created in the template 25 | test('IAM Permissions Created', () => { 26 | const app = new cdk.App(); 27 | // WHEN 28 | const stack = new Cdk.CdkStack(app, 'MyTestStack'); 29 | // THEN 30 | const template = Template.fromStack(stack); 31 | 32 | template.hasResource('AWS::IAM::Role', {}); 33 | }); 34 | -------------------------------------------------------------------------------- /test/lambda_function.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | # This function is used to test the conversion of Secrets to Lambda Environmental Variables 6 | # 7 | 8 | import os 9 | import json 10 | import sys 11 | 12 | def lambda_handler(event, context): 13 | print(f"Got event in main lambda [{event}]",flush=True) 14 | 15 | # Return all of the data 16 | return { 17 | 'statusCode': 200, 18 | 'layer': { 19 | 'EXAMPLE_CONNECTION_TOKEN': os.environ.get('EXAMPLE_CONNECTION_TOKEN', 'Not Set'), 20 | 'EXAMPLE_CLUSTER_ID': os.environ.get('EXAMPLE_CLUSTER_ID', 'Not Set'), 21 | 'EXAMPLE_CONNECTION_URL': os.environ.get('EXAMPLE_CONNECTION_URL', 'Not Set'), 22 | 'EXAMPLE_TENANT': os.environ.get('EXAMPLE_TENANT', 'Not Set'), 23 | 'AWS_LAMBDA_EXEC_WRAPPER': os.environ.get('AWS_LAMBDA_EXEC_WRAPPER', 'Not Set') 24 | }, 25 | 'secondLayer': { 26 | 'SECOND_LAYER_EXECUTE': os.environ.get('SECOND_LAYER_EXECUTE', 'Not Set') 27 | } 28 | } -------------------------------------------------------------------------------- /cdk/bin/cdk.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // 4 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // SPDX-License-Identifier: MIT-0 6 | // 7 | // The entry-point for CDK deployment of Lambda Layers and Test Functions 8 | // 9 | 10 | import 'source-map-support/register'; 11 | import * as cdk from 'aws-cdk-lib'; 12 | import { CdkStack } from '../lib/cdk-stack'; 13 | 14 | const app = new cdk.App(); 15 | new CdkStack(app, 'CdkStack', { 16 | /* If you don't specify 'env', this stack will be environment-agnostic. 17 | * Account/Region-dependent features and context lookups will not work, 18 | * but a single synthesized template can be deployed anywhere. */ 19 | 20 | /* Uncomment the next line to specialize this stack for the AWS Account 21 | * and Region that are implied by the current CLI configuration. */ 22 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, 23 | 24 | /* Uncomment the next line if you know exactly what Account and Region you 25 | * want to deploy the stack to. */ 26 | //env: { account: '123456789012', region: 'us-east-2' }, 27 | 28 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ 29 | }); 30 | -------------------------------------------------------------------------------- /cdk/lib/cdk-stack.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0 4 | // 5 | // This code is used to create lambda layers and a lambda function to 6 | // demonstrate conversion of data from AWS Secrets Manager to Lambda 7 | // Environmental Variables 8 | // 9 | import * as cdk from 'aws-cdk-lib'; 10 | import * as iam from 'aws-cdk-lib/aws-iam'; 11 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 12 | import { Construct } from 'constructs'; 13 | 14 | export class CdkStack extends cdk.Stack { 15 | constructor(scope: Construct, id: string, props?: cdk.StackProps) { 16 | super(scope, id, props); 17 | 18 | const secretArn = new cdk.CfnParameter(this, 'secretArn', { 19 | type: 'String', 20 | description: 'The ARN for the secret to read data from.'}); 21 | 22 | const apiTimeout = new cdk.CfnParameter(this, 'apiTimeout', { 23 | type: 'Number', 24 | description: 'The amount of time in milliseconds to allow for API execution.', 25 | default: 5000}); 26 | 27 | const secretRegion = cdk.Stack.of(this).region; 28 | 29 | // Create a new policy document 30 | const lambdaPolicy = new iam.PolicyDocument(); 31 | lambdaPolicy.addStatements(new iam.PolicyStatement({ 32 | effect: iam.Effect.ALLOW, 33 | actions: ['secretsmanager:GetSecretValue'], 34 | resources: [secretArn.valueAsString] 35 | })); 36 | 37 | // Create the role here to use the secret 38 | const lambdaRole = new iam.Role(this, 'lambda-layer-example-role', { 39 | assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), 40 | description: 'Used to gain access to a Secret stored in SecretManager', 41 | inlinePolicies: {lambdaPolicy} 42 | }); 43 | 44 | // Create the lambda layer 45 | const getSecretsLayer = new lambda.LayerVersion(this, 'get-secrets-layer', { 46 | code: lambda.Code.fromAsset('../out/get-secrets-layer.zip'), 47 | description: 'This layer is used to pull secrets from secret manager and convert to environmental variables', 48 | removalPolicy: cdk.RemovalPolicy.DESTROY // NOTE: you wouldn't do this in prod, but it's fine for a dev case 49 | }); 50 | 51 | const secondExampleLayer = new lambda.LayerVersion(this, 'second-example-layer', { 52 | code: lambda.Code.fromAsset('../out/second-example-layer.zip'), 53 | description: 'This layer is used for example purposes', 54 | removalPolicy: cdk.RemovalPolicy.DESTROY // NOTE: you wouldn't do this for prod, but it's fine in this case 55 | }); 56 | 57 | // Create the lambda and assign the role and layer 58 | const func = new lambda.Function(this, 'example-get-secrets-lambda', { 59 | runtime: lambda.Runtime.PYTHON_3_9, 60 | handler: 'lambda_function.lambda_handler', 61 | code: lambda.Code.fromAsset('../out/example-get-secrets-lambda.zip'), 62 | layers: [getSecretsLayer, secondExampleLayer], 63 | role: lambdaRole, 64 | environment: { 65 | 'AWS_LAMBDA_EXEC_WRAPPER': '/opt/get-secrets-layer', 66 | 'SECRET_REGION': secretRegion, 67 | 'SECRET_ARN': secretArn.valueAsString, 68 | 'API_TIMEOUT': apiTimeout.valueAsString 69 | } 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /src/get-secrets-layer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # SPDX-License-Identifier: MIT-0 6 | # 7 | # This script is used to coordinate the creation of environmental variables by using a Golang executable 8 | # that is part of the same layer. In order for this layer to operate, it must have permissions to 9 | # retrieve data from Secrets Manager. 10 | # 11 | # Please note, this example uses AWS Lambda environmental variables. This includes the ARN of the Secret 12 | # to use, the region to pull the Secret from, and the ARN for the role to use to retrieve the Secert. 13 | # 14 | # To use this script, make sure that it is wrapped in a ZIP file along with the go-retrieve-secret 15 | # Golang executable and added as a Lambda Layer. Your Lambda must reference this script by setting the 16 | # AWS_LAMBDA_EXEC_WRAPPER environmental variable to /opt/get-secrets-layer (the name of this shell script) 17 | # and by referencing this layer within it's configuration. 18 | # 19 | 20 | # The name of this script 21 | name=$(basename $0) 22 | 23 | # The full path to this script 24 | fullPath=$(dirname $(readlink -f $0)) 25 | 26 | # The path to the interpreter and all of the originally intended arguments 27 | args=("$@") 28 | 29 | # The name of the secret 30 | secretArn="${SECRET_ARN}" 31 | 32 | # The region to use for the API calls 33 | region="${SECRET_REGION}" 34 | 35 | # The role to use for API calls 36 | roleName="${ASSUME_ROLE_ARN}" 37 | 38 | # The timeout for API calls 39 | timeout="${API_TIMEOUT}" 40 | 41 | # Create a temp file to hold values to be exported 42 | tempFile=$(mktemp /tmp/${name}.XXXXXXX) 43 | last_cmd=$? 44 | if [[ ${last_cmd} -ne 0 ]]; then 45 | echo "Failed to create a temp file" 46 | exit 1 47 | fi 48 | 49 | # Get the secret value by calling the Go executable 50 | values=$(${fullPath}/go-retrieve-secret -r "${region}" -s "${secretArn}" -a "${roleName}" -t "${timeout}") 51 | last_cmd=$? 52 | 53 | # Verify that the last command was successful 54 | if [[ ${last_cmd} -ne 0 ]]; then 55 | echo "Failed to setup environment for Secret ${secretArn}" 56 | exit 1 57 | fi 58 | 59 | # Read the data line by line and export the data as key value pairs 60 | # and environmental variables 61 | echo "${values}" | while read -r line; do 62 | 63 | # Split the line into a key and value 64 | ARRY=(${line//|/ }) 65 | 66 | # Capture the key value 67 | key="${ARRY[0]}" 68 | 69 | # Since the key had been captured, no need to keep it in the array 70 | unset ARRY[0] 71 | 72 | # Join the other parts of the array into a single value. There is a chance that 73 | # The split may have broken the data into multiple values. This will force the 74 | # data to be rejoined. 75 | value="${ARRY[@]}" 76 | 77 | # Save as an env var to the temp file for later processing 78 | echo "export ${key}=\"${value}\"" >> ${tempFile} 79 | done 80 | 81 | # Source the temp file to read in the env vars 82 | . ${tempFile} 83 | 84 | # Determine if AWS_LAMBDA_EXEC_WRAPPER points to this layer 85 | # This is necessary as the Secret may have not specified a 86 | # new layer. Without checking, the lambda layer may be 87 | # called again. 88 | layer_name=$(basename ${AWS_LAMBDA_EXEC_WRAPPER}) 89 | 90 | if [[ "${layer_name}" == "${name}" ]]; then 91 | echo "No new layer was specified, unsetting AWS_LAMBDA_EXEC_WRAPPER" 92 | unset AWS_LAMBDA_EXEC_WRAPPER 93 | else 94 | # Set args to include the new layer 95 | args=("${AWS_LAMBDA_EXEC_WRAPPER}" "${args[@]}") 96 | fi 97 | 98 | # Remove the temp file 99 | rm ${tempFile} > /dev/null 2>&1 100 | 101 | # Execute the next step 102 | exec ${args[@]} 103 | -------------------------------------------------------------------------------- /src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.38.9 h1:eTIU8cggkcJwOT+Fb05OG8UhtSap5EWdWl0dMQFCdr8= 2 | github.com/aws/aws-sdk-go v1.38.9/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= 3 | github.com/aws/aws-sdk-go v1.40.35 h1:ofWh1LlWaSbOpAsl8EHlg96PZXqgCGKKi8YgrdU2Z+I= 4 | github.com/aws/aws-sdk-go v1.40.35/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= 5 | github.com/aws/aws-sdk-go-v2 v1.9.0 h1:+S+dSqQCN3MSU5vJRu1HqHrq00cJn6heIMU7X9hcsoo= 6 | github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= 7 | github.com/aws/aws-sdk-go-v2/config v1.7.0 h1:J2cZ7qe+3IpqBEXnHUrFrOjoB9BlsXg7j53vxcl5IVg= 8 | github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= 9 | github.com/aws/aws-sdk-go-v2/credentials v1.4.0 h1:kmvesfjY861FzlCU9mvAfe01D9aeXcG2ZuC+k9F2YLM= 10 | github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY= 11 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0 h1:OxTAgH8Y4BXHD6PGCJ8DHx2kaZPCQfSTqmDsdRZFezE= 12 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y= 13 | github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2 h1:d95cddM3yTm4qffj3P6EnP+TzX1SSkWaQypXSgT/hpA= 14 | github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw= 15 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0 h1:VNJ5NLBteVXEwE2F1zEXVmyIH58mZ6kIQGJoC7C+vkg= 16 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk= 17 | github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.6.0 h1:3vxYnnbPWwECs3xN+cu/bRefhynMOH6elQAxuHES01Q= 18 | github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.6.0/go.mod h1:B+7C5UKdVq1ylkI/A6O8wcurFtaux0R1njePNPtKwoA= 19 | github.com/aws/aws-sdk-go-v2/service/ssm v1.10.0 h1:kEYH8NMfMA5gC5MMcEr5gVtJxyGmaxIYJwwZ7T6ygNs= 20 | github.com/aws/aws-sdk-go-v2/service/ssm v1.10.0/go.mod h1:4dXS5YNqI3SNbetQ7X7vfsMlX6ZnboJA2dulBwJx7+g= 21 | github.com/aws/aws-sdk-go-v2/service/sso v1.4.0 h1:sHXMIKYS6YiLPzmKSvDpPmOpJDHxmAUgbiF49YNVztg= 22 | github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA= 23 | github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 h1:1at4e5P+lvHNl2nUktdM2/v+rpICg/QSEr9TO/uW9vU= 24 | github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM= 25 | github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc= 26 | github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= 27 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 31 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 32 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 33 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 36 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 37 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 38 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 39 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 40 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 49 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 50 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 51 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 53 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 54 | -------------------------------------------------------------------------------- /src/go-retrieve-secret.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0 4 | // 5 | // This code is used to retrieve values from AWS Secrets Manager and to output the 6 | // decrypted values for conversion into Lambda Environmental Variables. 7 | // 8 | package main 9 | 10 | import ( 11 | "context" 12 | "flag" 13 | "fmt" 14 | "time" 15 | 16 | "encoding/json" 17 | 18 | "github.com/aws/aws-sdk-go-v2/aws" 19 | "github.com/aws/aws-sdk-go-v2/credentials" 20 | "github.com/aws/aws-sdk-go-v2/service/secretsmanager" 21 | "github.com/aws/aws-sdk-go-v2/service/sts" 22 | 23 | "github.com/aws/aws-sdk-go-v2/aws/retry" 24 | "github.com/aws/aws-sdk-go-v2/config" 25 | ) 26 | 27 | // Constants for default values if none are supplied 28 | const DEFAULT_TIMEOUT = 5000 29 | const DEFAULT_REGION = "us-east-2" 30 | const DEFAULT_SESSION = "param_session" 31 | 32 | var ( 33 | region string 34 | secretArn string 35 | roleArn string 36 | timeout int 37 | sessionName string 38 | ) 39 | 40 | // The main function will pull command line arg and retrieve the secret. The resulting 41 | // secret will be dumped as JSON to the output 42 | func main() { 43 | 44 | // Get all of the command line data and perform the necessary validation 45 | getCommandParams() 46 | 47 | // Setup a new context to allow for limited execution time for API calls with a default of 200 milliseconds 48 | ctx, cancel := context.WithTimeout(context.TODO(), time.Duration(timeout)*time.Millisecond) 49 | defer cancel() 50 | 51 | // Load the config 52 | cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region), config.WithRetryer(func() aws.Retryer { 53 | // NopRetryer is used here in a global context to avoid retries on API calls 54 | return retry.AddWithMaxAttempts(aws.NopRetryer{}, 1) 55 | })) 56 | 57 | if err != nil { 58 | panic("configuration error " + err.Error()) 59 | } 60 | 61 | // Assume a role to retreive the parameter 62 | role, err := AttemptAssumeRole(ctx, cfg) 63 | 64 | if err != nil { 65 | panic("Failed to assume role due to error " + err.Error()) 66 | } 67 | 68 | // Get the secret 69 | result, err := GetSecret(ctx, cfg, role) 70 | 71 | if err != nil { 72 | panic("Failed to retrieve secret due to error " + err.Error()) 73 | } 74 | 75 | // Convert the secret into JSON 76 | var dat map[string]interface{} 77 | 78 | // Convert the secret to JSON 79 | if err := json.Unmarshal([]byte(*result.SecretString), &dat); err != nil { 80 | fmt.Println("Failed to convert Secret to JSON") 81 | fmt.Println(err) 82 | panic(err) 83 | } 84 | 85 | // Get the secret value and dump the output in a manner that a shell script can read the 86 | // data from the output 87 | for key, value := range dat { 88 | fmt.Printf("%s|%s\n", key, value) 89 | } 90 | } 91 | 92 | func getCommandParams() { 93 | // Setup command line args 94 | flag.StringVar(®ion, "r", DEFAULT_REGION, "The Amazon Region to use") 95 | flag.StringVar(&secretArn, "s", "", "The ARN for the secret to access") 96 | flag.StringVar(&roleArn, "a", "", "The ARN for the role to assume for Secret Access") 97 | flag.IntVar(&timeout, "t", DEFAULT_TIMEOUT, "The amount of time to wait for any API call") 98 | flag.StringVar(&sessionName, "n", DEFAULT_SESSION, "The name of the session for AWS STS") 99 | 100 | // Parse all of the command line args into the specified vars with the defaults 101 | flag.Parse() 102 | 103 | // Verify that the correct number of args were supplied 104 | if len(region) == 0 || len(secretArn) == 0 { 105 | flag.PrintDefaults() 106 | panic("You must supply a region and secret ARN. -r REGION -s SECRET-ARN [-a ARN for ROLE -t TIMEOUT IN MILLISECONDS -n SESSION NAME]") 107 | } 108 | } 109 | 110 | // This function will attempt to assume the supplied role and return either an error or the assumed role 111 | func AttemptAssumeRole(ctx context.Context, cfg aws.Config) (*sts.AssumeRoleOutput, error) { 112 | if len(roleArn) <= 0 { 113 | return nil, nil 114 | } 115 | 116 | client := sts.NewFromConfig(cfg) 117 | 118 | return client.AssumeRole(ctx, 119 | &sts.AssumeRoleInput{ 120 | RoleArn: &roleArn, 121 | RoleSessionName: &sessionName, 122 | }, 123 | ) 124 | } 125 | 126 | // This function will return the descrypted version of the Secret from Secret Manager using the supplied 127 | // assumed role to interact with Secret Manager. This function will return either an error or the 128 | // retrieved and decrypted secret. 129 | func GetSecret(ctx context.Context, cfg aws.Config, assumedRole *sts.AssumeRoleOutput) (*secretsmanager.GetSecretValueOutput, error) { 130 | 131 | if assumedRole != nil { 132 | client := secretsmanager.NewFromConfig(cfg, func(o *secretsmanager.Options) { 133 | o.Credentials = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(*assumedRole.Credentials.AccessKeyId, *assumedRole.Credentials.SecretAccessKey, *assumedRole.Credentials.SessionToken)) 134 | }) 135 | return client.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{ 136 | SecretId: aws.String(secretArn), 137 | }) 138 | } else { 139 | client := secretsmanager.NewFromConfig(cfg) 140 | return client.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{ 141 | SecretId: aws.String(secretArn), 142 | }) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Creating AWS Lambda environment variable from AWS Secrets Manager 2 | 3 | [AWS Lambda](https://aws.amazon.com/lambda/) layers and extensions are used by third-party software providers for monitoring Lambda functions. A monitoring solution needs environmental variables to provide configuration information to send metric information to an endpoint. 4 | 5 | Managing this information as environmental variables across thousands of Lambda functions creates operational overhead. Instead, you can use the approach in this blog post to create environmental variables dynamically from information hosted in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). 6 | 7 | This can help avoid managing secret rotation for individual functions. It ensures that values stay encrypted until runtime, and abstracts away the management of the environmental variables. 8 | 9 | ## Overview 10 | 11 | This post shows how to create a Lambda layer for Node.js, Python, Ruby, Java, and .NET Core runtimes. It retrieves values from Secrets Manager and converts the secret into an environmental variable that can be used by other layers and functions. The Lambda layer uses a [wrapper script](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-modify.html#runtime-wrapper) to fetch information from Secrets Manager and create environmental variables. 12 | 13 | ![Process Flow](overview.png) 14 | 15 | The steps in the process are as follows: 16 | 17 | 1. The Lambda service responds to an event and initializes the Lambda context. 18 | 1. The wrapper script is called as part of the Lambda init phase. 19 | 1. The wrapper script calls a Golang executable passing in the ARN for the secret to retrieve. 20 | 1. The Golang executable uses the Secrets Manager API to retrieve the decrypted secret. 21 | 1. The wrapper script converts the information into environmental variables and calls the next step in processing. 22 | 23 | ## The wrapper script 24 | 25 | The wrapper script is the main entry-point for the extension and is called by the Lambda service as part of the init phase. During this phase, the wrapper script will read in basic information from the environment and call the Golang executable. If there was an issue with the Golang executable, the wrapper script will log a statement and exit with an error. 26 | 27 | ```bash 28 | # Get the secret value by calling the Go executable 29 | values=$(${fullPath}/go-retrieve-secret -r "${region}" -s "${secretArn}" -a "${roleName}" -t ${timeout}) 30 | last_cmd=$? 31 | 32 | # Verify that the last command was successful 33 | if [[ ${last_cmd} -ne 0 ]]; then 34 | echo "Failed to setup environment for Secret ${secretArn}" 35 | exit 1 36 | fi 37 | ``` 38 | ## Golang executable 39 | 40 | This uses Golang to invoke the AWS APIs since the Lambda execution environment does natively provide the AWS Command Line Interface. The Golang executable can be included in a layer so that the layer works with a number of Lambda runtimes. 41 | 42 | The Golang executable captures and validates the command line arguments to ensure that required parameters are supplied. If Lambda does not have permissions to read and decrypt the secret, you can supply an ARN for a role to assume. 43 | 44 | The following code example shows how the Golang executable retrieves the necessary information to assume a role using the AWS Security Token Service: 45 | 46 | ```go 47 | client := sts.NewFromConfig(cfg) 48 | 49 | return client.AssumeRole(ctx, 50 | &sts.AssumeRoleInput{ 51 | RoleArn: &roleArn, 52 | RoleSessionName: &sessionName, 53 | }, 54 | ) 55 | ``` 56 | 57 | After obtaining the necessary permissions, the secret can be retrieved using the Secrets Manager API. The following code example uses the new credentials to create a client connection to Secrets Manager and the secret: 58 | 59 | ```go 60 | client := secretsmanager.NewFromConfig(cfg, func(o *secretsmanager.Options) { 61 | o.Credentials = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(*assumedRole.Credentials.AccessKeyId, *assumedRole.Credentials.SecretAccessKey, *assumedRole.Credentials.SessionToken)) 62 | }) 63 | return client.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{ 64 | SecretId: aws.String(secretArn), 65 | }) 66 | ``` 67 | 68 | After retrieving the secret, the contents must be converted into a format that the wrapper script can use. The following sample code covers the conversion from a secret string to JSON by storing the data in a map. Once the data is in a map, a loop is used to output the information as key-value pairs. 69 | 70 | ```go 71 | // Convert the secret into JSON 72 | var dat map[string]interface{} 73 | 74 | // Convert the secret to JSON 75 | if err := json.Unmarshal([]byte(*result.SecretString), &dat); err != nil { 76 | fmt.Println("Failed to convert Secret to JSON") 77 | fmt.Println(err) 78 | panic(err) 79 | } 80 | 81 | // Get the secret value and dump the output in a manner that a shell script can read the 82 | // data from the output 83 | for key, value := range dat { 84 | fmt.Printf("%s|%s\n", key, value) 85 | } 86 | ``` 87 | 88 | ## Conversion to environmental variables 89 | 90 | After the secret information is retrieved by using Golang, the wrapper script can now loop over the output, populate a temporary file with export statements, and execute the temporary file. The following code covers these steps: 91 | 92 | ```bash 93 | # Read the data line by line and export the data as key value pairs 94 | # and environmental variables 95 | echo "${values}" | while read -r line; do 96 | 97 | # Split the line into a key and value 98 | ARRY=(${line//|/ }) 99 | 100 | # Capture the kay value 101 | key="${ARRY[0]}" 102 | 103 | # Since the key had been captured, no need to keep it in the array 104 | unset ARRY[0] 105 | 106 | # Join the other parts of the array into a single value. There is a chance that 107 | # The split man have broken the data into multiple values. This will force the 108 | # data to be rejoined. 109 | value="${ARRY[@]}" 110 | 111 | # Save as an env var to the temp file for later processing 112 | echo "export ${key}=\"${value}\"" >> ${tempFile} 113 | done 114 | 115 | # Source the temp file to read in the env vars 116 | . ${tempFile} 117 | ``` 118 | 119 | At this point, the information stored in the secret is now available as environmental variables to layers and the Lambda function. 120 | 121 | ## Deployment 122 | 123 | To deploy this solution, you must build on an instance that is running an [Amazon Linux 2 AMI](https://aws.amazon.com/amazon-linux-2/). This ensures that the compiled Golang executable is compatible with the Lambda execution environment. 124 | 125 | The easiest way to deploy this solution is from an [AWS Cloud9](https://aws.amazon.com/cloud9/) environment but you can also use an [Amazon EC2](https://aws.amazon.com/ec2/) instance. To build and deploy the solution into your environment, you need the ARN of the secret that you want to use. A build script is provided to ease deployment and perform compilation, archival, and [AWS CDK](https://aws.amazon.com/cdk/) execution. 126 | 127 | To deploy, run: 128 | 129 | ```sh 130 | ./build.sh 131 | ``` 132 | 133 | Once the build is complete, the following resources are deployed into your AWS account: 134 | 135 | 1. A Lambda layer (called get-secrets-layer) 136 | 1. A second Lambda layer for testing (called second-example-layer) 137 | 1. A Lambda function (called example-get-secrets-lambda) 138 | 139 | ## Testing 140 | 141 | To test the deployment, create a test event to send to the new **example-get-secrets-lambda** Lambda function using the AWS Management Console. The test Lambda function uses both the **get-secrets-layer** and **second-example-layer** Lambda layers, and the secret specified from the build. This function logs the values of environmental variables that were created by the get-secrets-layer and second-example-layer layers: 142 | 143 | The secret contains the following information: 144 | 145 | ```json 146 | { 147 | "EXAMPLE_CONNECTION_TOKEN": "EXAMPLE AUTH TOKEN", 148 | "EXAMPLE_CLUSTER_ID": "EXAMPLE CLUSTER ID", 149 | "EXAMPLE_CONNECTION_URL": "EXAMPLE CONNECTION URL", 150 | "EXAMPLE_TENANT": "EXAMPLE TENANT", 151 | "AWS_LAMBDA_EXEC_WRAPPER": "/opt/second-example-layer" 152 | } 153 | ``` 154 | 155 | This is the Python code for the example-get-secrets-lambda function: 156 | 157 | ```python 158 | import os 159 | import json 160 | import sys 161 | 162 | def lambda_handler(event, context): 163 | print(f"Got event in main lambda [{event}]",flush=True) 164 | 165 | # Return all of the data 166 | return { 167 | 'statusCode': 200, 168 | 'layer': { 169 | 'EXAMPLE_AUTH_TOKEN': os.environ.get('EXAMPLE_AUTH_TOKEN', 'Not Set'), 170 | 'EXAMPLE_CLUSTER_ID': os.environ.get('EXAMPLE_CLUSTER_ID', 'Not Set'), 171 | 'EXAMPLE_CONNECTION_URL': os.environ.get('EXAMPLE_CONNECTION_URL', 'Not Set'), 172 | 'EXAMPLE_TENANT': os.environ.get('EXAMPLE_TENANT', 'Not Set'), 173 | 'AWS_LAMBDA_EXEC_WRAPPER': os.environ.get('AWS_LAMBDA_EXEC_WRAPPER', 'Not Set') 174 | }, 175 | 'secondLayer': { 176 | 'SECOND_LAYER_EXECUTE': os.environ.get('SECOND_LAYER_EXECUTE', 'Not Set') 177 | } 178 | } 179 | ``` 180 | 181 | When running a test using the AWS Management Console, you see the following response returned from the Lambda in the AWS Management Console: 182 | 183 | ```json 184 | { 185 | "statusCode": 200, 186 | "layer": { 187 | "EXAMPLE_AUTH_TOKEN": "EXAMPLE AUTH TOKEN", 188 | "EXAMPLE_CLUSTER_ID": "EXAMPLE CLUSTER ID", 189 | "EXAMPLE_CONNECTION_URL": "EXAMPLE CONNECTION URL", 190 | "EXAMPLE_TENANT": "EXAMPLE TENANT", 191 | "AWS_LAMBDA_EXEC_WRAPPER": "/opt/second-example-layer" 192 | }, 193 | "secondLayer": { 194 | "SECOND_LAYER_EXECUTE": "true" 195 | } 196 | } 197 | ``` 198 | 199 | > When the secret changes, there is a delay before those changes are available to the Lambda layers and function. This is because the layer only executes in the init phase of the Lambda lifecycle. After the Lambda execution environment is recreated and initialized, the layer executes and creates environmental variables with the new secret information. 200 | 201 | ## Conclusion 202 | 203 | This solution provides a way to convert information from Secrets Manager into Lambda environment variables. By following this approach, you can centralize the management of information through Secrets Manager, instead of at the function level. 204 | 205 | For more information about the Lambda lifecycle, see the [Lambda execution environment lifecycle](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html#runtimes-lifecycle) documentation. 206 | 207 | ## Security 208 | 209 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 210 | 211 | ## License 212 | 213 | This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file. --------------------------------------------------------------------------------