in packages/aws-rfdk/lib/core/lib/staticip-server.ts [311:380]
protected setupLifecycleEventHandlerFunction(): LambdaFunction {
const stack = Stack.of(this);
// The SingletonFunction does not tell us when it's newly created vs. finding a pre-existing
// one. So, we do our own singleton Function so that we know when it's the first creation, and, thus,
// we must attach one-time permissions.
const functionUniqueId = 'AttachEniToInstance' + this.removeHyphens('83a5dca5-db54-4aa4-85d2-8d419cdf85ce');
let singletonPreExists: boolean = true;
let eventHandler = stack.node.tryFindChild(functionUniqueId) as LambdaFunction;
if (!eventHandler) {
const handlerCode = Code.fromAsset(path.join(__dirname, '..', '..', 'lambdas', 'nodejs', 'asg-attach-eni'), {
exclude: ['**/*', '!index*'],
});
eventHandler = new LambdaFunction(stack, functionUniqueId, {
code: handlerCode,
handler: 'index.handler',
runtime: Runtime.NODEJS_18_X,
description: `Created by RFDK StaticPrivateIpServer to process instance launch lifecycle events in stack '${stack.stackName}'. This lambda attaches an ENI to newly launched instances.`,
logRetention: RetentionDays.THREE_DAYS,
});
singletonPreExists = false;
}
// Note: We **cannot** reference the ASG's ARN in the lambda's policy. It would create a deadlock at deployment:
// Lambda policy waiting on ASG completion to get ARN
// -> lambda waiting on policy to be created
// -> ASG waiting on lambda to signal lifecycle continue for instance start
// -> back to the start of the cycle.
// Instead we use resourcetags condition to limit the scope of the lambda.
const tagKey = 'RfdkStaticPrivateIpServerGrantConditionKey';
const tagValue = Names.uniqueId(eventHandler);
const grantCondition: { [key: string]: string } = {};
grantCondition[`autoscaling:ResourceTag/${tagKey}`] = tagValue;
Tags.of(this.autoscalingGroup).add(tagKey, tagValue);
// Allow the lambda to complete the lifecycle action for only tagged ASGs.
const iamCompleteLifecycle = new PolicyStatement({
effect: Effect.ALLOW,
actions: [
'autoscaling:CompleteLifecycleAction',
],
resources: [
`arn:${stack.partition}:autoscaling:${stack.region}:${stack.account}:autoScalingGroup:*:autoScalingGroupName/*`,
],
conditions: {
'ForAnyValue:StringEquals': grantCondition,
},
});
eventHandler.role!.addToPrincipalPolicy(iamCompleteLifecycle);
if (!singletonPreExists) {
// Allow the lambda to attach the ENI to the instance that was created.
// Referencing: https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazonec2.html
// Last-Accessed: July 2020
// The ec2:DescribeNetworkInterfaces, and ec2:AttachNetworkInterface operations
// do not support conditions, and do not support resource restriction.
// So, we only attach the policy to the lambda function once; when we first create it.
const iamEniAttach = new PolicyStatement({
effect: Effect.ALLOW,
actions: [
'ec2:DescribeNetworkInterfaces',
'ec2:AttachNetworkInterface',
],
resources: ['*'],
});
eventHandler.role!.addToPrincipalPolicy(iamEniAttach);
}
return eventHandler;
}