Skip to content

Instantly share code, notes, and snippets.

@rlmartin
Created June 4, 2018 02:26
Show Gist options
  • Save rlmartin/94cf7de057cc4acaac78f108bd227259 to your computer and use it in GitHub Desktop.
Save rlmartin/94cf7de057cc4acaac78f108bd227259 to your computer and use it in GitHub Desktop.
CloudFormation resource for AWS SecretsManager
AWSTemplateFormatVersion: 2010-09-09
Description: Manages AWS SecretsManager for CloudFormation
Resources:
Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: LogWriter
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource: '*'
- PolicyName: SecretManager
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- secretsmanager:*
Effect: Allow
Resource: '*'
- Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey
Effect: Allow
Resource: '*'
Function:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
const AWS = require('aws-sdk')
const sm = new AWS.SecretsManager()
exports.handler = (event, context, callback) => {
var params = event.ResourceProperties
delete params.ServiceToken
switch (event.RequestType.toLowerCase()) {
case 'create':
createSecret(params, (err, data) => returnToCF(err, data, event, context, callback))
break;
case 'delete':
sm.deleteSecret({
SecretId: params.Name
}, (err, data) => returnToCF(err, data, event, context, callback))
break;
case 'update':
const prevParams = event.OldResourceProperties
sm.deleteSecret({
SecretId: prevParams.Name
}, function(err, data) {
if (err) returnToCF(err, null, event, context, callback)
else {
createSecret(params, (err, data) => returnToCF(err, data, event, context, callback))
}
})
break;
default:
returnToCF('RequestType [' + event.RequestType + '] not supported.', null, event, context, (err, data) => returnToCF(err, data, event, context, callback))
break;
}
}
function createSecret(params, callback) {
sm.createSecret(params, (err, data) => {
if (err && err.code === 'ResourceExistsException') {
sm.restoreSecret({ SecretId: params.Name }, (err, data) => {
params.SecretId = params.Name
delete params.Name
sm.updateSecret(params, callback)
})
} else {
callback(null, data)
}
})
}
function returnToCF(err, data, event, context, callback) {
const result = {
LogicalResourceId: event.LogicalResourceId,
PhysicalResourceId: event.PhysicalResourceId || context.logStreamName,
RequestId: event.RequestId,
StackId: event.StackId
}
if (err) {
result.Status = 'FAILED'
console.log(JSON.stringify(err))
result.Reason = err.toString()
} else {
result.Status = 'SUCCESS'
result.Data = data
}
const responseBody = JSON.stringify(result)
const https = require("https");
const url = require("url");
const parsedUrl = url.parse(event.ResponseURL);
const options = {
hostname: parsedUrl.hostname,
port: 443,
path: parsedUrl.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": responseBody.length
}
};
console.log("SENDING RESPONSE...\n");
const request = https.request(options, function(response) {
callback(null, result)
});
request.on("error", function(error) {
callback(error)
});
// write data to request body
request.write(responseBody);
request.end();
}
Description: Manages AWS SecretsManager for CloudFormation
FunctionName: !Ref AWS::StackName
Handler: index.handler
MemorySize: 128
Role: !GetAtt Role.Arn
Runtime: nodejs6.10
Timeout: 30
DependsOn:
- Role
FunctionLogRetention:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/lambda/${Function}'
RetentionInDays: 7
DependsOn:
- Function
Outputs:
Function:
Description: The name of the function.
Value: !Ref Function
AWSTemplateFormatVersion: 2010-09-09
Description: Create Bright database.
Parameters:
Password:
Description: The initial password used. This may get changed during credential rotation.
Type: String
SecretsManagerFunc:
Description: The name of the Lambda function that serves as a custom resource on top of AWS SecretsManager.
Type: String
Default: cf-secretsmanager
Resources:
Config:
Type: Custom::SecretsManager
Properties:
ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${SecretsManagerFunc}'
Name: your-name-here
Description: your-description
SecretString: !Sub '{"password":"${Password}"}'
@rlmartin
Copy link
Author

rlmartin commented Jun 4, 2018

Deploy the first CloudFormation template to create the Lambda function that provides the functionality, then use the second CloudFormation template as an example of how to use it in a Custom Resource.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment