Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

9.1.0 - IAM role management #395

Merged
merged 14 commits into from
Jun 13, 2024
2 changes: 1 addition & 1 deletion bin/dead-letter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/* eslint-disable no-console */

const { CloudFormation } = require('@aws-sdk/client-cloudformation')
const { CloudFormation } = require('@aws-sdk/client-cloudformation');
const { SQS } = require('@aws-sdk/client-sqs');
const inquirer = require('inquirer');
const stream = require('stream');
Expand Down
23 changes: 23 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
### 9.1.0

- Merge ScalingLambdaRole and LambdaTotalMessagesRole into the primary WatchbotRole resource to reduce number of distinct IAM roles
- Add option `autoScalingRole` for using a predefined auto scaling role instead of creating one for the stack. This role should have the following permissions, with the resouce scope being as strict as desired:

```JSON
{
"Statement": [
{
"Action": [
"application-autoscaling:*",
"cloudwatch:DescribeAlarms",
"cloudwatch:PutMetricAlarm",
"ecs:UpdateService",
"ecs:DescribeServices"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
```

### 9.0.1

- Bug fix: TotalMessagesLambda now working as expected; watchbot stacks now scaling down when no tasks in queue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ const Resources = {
Owner: 'mapbox',
Repo: 'ecs-watchbot',
PollForSourceChanges: 'false',
Branch: 'master',
Branch: '9.x',
OAuthToken: '{{resolve:secretsmanager:code-pipeline-helper/access-token}}'
}
}
Expand Down
23 changes: 23 additions & 0 deletions docs/building-a-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ When creating your watchbot stacks with the `watchbot.template()` method, you no
**placementConstraints** | ECS service [placement constraints](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-placementconstraint.html). This value is ignored for `capacity` values other than `'EC2'`. | Object[]/Ref | No | false
**placementStrategies** | ECS service [placement strategies](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-placementstrategy.html). This value is ignored for `capacity` values other than `'EC2'`. | Object[]/Ref | No | false
**structuredLogging** | Whether to emit logs in JSON format or not | Boolean | No | `false`
**autoscalingRoleArn** | A custom autoscaling role to use instead of building a distinct role for the stack | String/Ref | No | If not provided, an autoscaling role will be built with the permissions described in [Custom Autoscaling Role](#custom-autoscaling-role)
LandonHarvey marked this conversation as resolved.
Show resolved Hide resolved

### writableFilesystem mode explained

Expand Down Expand Up @@ -137,3 +138,25 @@ var outputs = {

cloudfriend.merge(myTemplate, watcher, outputs);
```

### Custom Autoscaling Role

You can provide a custom autoscaling role for your service. If you do not provide a custom role, a role with the following permissions will be created, which can only be assumed by the `application-autoscaling.amazonaws.com` principal.

```JSON
{
"Statement": [
{
"Action": [
"application-autoscaling:*",
"cloudwatch:DescribeAlarms",
"cloudwatch:PutMetricAlarm",
"ecs:UpdateService",
"ecs:DescribeServices"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
```
190 changes: 73 additions & 117 deletions lib/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ module.exports = (options = {}) => {
dashboard: true,
fifo: false,
fargatePublicIp: 'DISABLED',
structuredLogging: false
structuredLogging: false,
autoscalingRoleArn: false
},
options
);
Expand Down Expand Up @@ -246,7 +247,7 @@ module.exports = (options = {}) => {
Statement: [
{
Effect: 'Allow',
Principal: { Service: ['ecs-tasks.amazonaws.com'] },
Principal: { Service: ['ecs-tasks.amazonaws.com', 'lambda.amazonaws.com'] },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The roles created in ma-iam only had the service principal of ecs.amazonaws.com. Will that cause issues when interacting with the lambdas and should we add that in?

Action: ['sts:AssumeRole']
}
]
Expand Down Expand Up @@ -285,6 +286,39 @@ module.exports = (options = {}) => {
)
]
}
},
// roles for log-based lambda scaling utility
{
PolicyName: cf.join([cf.stackName, '-lambda-scaling']),
PolicyDocument: {
Statement: [
{
Effect: 'Allow',
Action: [
'logs:*'
],
Resource: cf.join([
'arn:',
cf.partition,
':logs:*:*:*'
])
},
{
Effect: 'Allow',
Action: [
'cloudwatch:PutMetricData'
],
Resource: '*'
},
{
Effect: 'Allow',
Action: [
'sqs:GetQueueAttributes'
],
Resource: cf.getAtt(prefixed('Queue'), 'Arn')
}
]
}
}
]
}
Expand Down Expand Up @@ -433,41 +467,43 @@ module.exports = (options = {}) => {
Resources[prefixed('Service')].Properties.PlacementStrategies =
options.placementStrategies;

Resources[prefixed('ScalingRole')] = {
Type: 'AWS::IAM::Role',
Properties: {
AssumeRolePolicyDocument: {
Statement: [
if (!options.autoscalingRoleArn) {
Resources[prefixed('ScalingRole')] = {
Type: 'AWS::IAM::Role',
Properties: {
AssumeRolePolicyDocument: {
Statement: [
{
Effect: 'Allow',
Principal: { Service: ['application-autoscaling.amazonaws.com'] },
Action: ['sts:AssumeRole']
}
]
},
Path: '/',
Policies: [
{
Effect: 'Allow',
Principal: { Service: ['application-autoscaling.amazonaws.com'] },
Action: ['sts:AssumeRole']
PolicyName: 'watchbot-autoscaling',
PolicyDocument: {
Statement: [
{
Effect: 'Allow',
Action: [
'application-autoscaling:*',
'cloudwatch:DescribeAlarms',
'cloudwatch:PutMetricAlarm',
'ecs:UpdateService',
'ecs:DescribeServices'
],
Resource: '*'
}
]
}
}
]
},
Path: '/',
Policies: [
{
PolicyName: 'watchbot-autoscaling',
PolicyDocument: {
Statement: [
{
Effect: 'Allow',
Action: [
'application-autoscaling:*',
'cloudwatch:DescribeAlarms',
'cloudwatch:PutMetricAlarm',
'ecs:UpdateService',
'ecs:DescribeServices'
],
Resource: '*'
}
]
}
}
]
}
};
}
};
}

Resources[prefixed('ScalingTarget')] = {
Type: 'AWS::ApplicationAutoScaling::ScalableTarget',
Expand All @@ -482,7 +518,7 @@ module.exports = (options = {}) => {
]),
MinCapacity: options.minSize,
MaxCapacity: options.maxSize,
RoleARN: cf.getAtt(prefixed('ScalingRole'), 'Arn')
RoleARN: options.autoscalingRoleArn || cf.getAtt(prefixed('ScalingRole'), 'Arn')
}
};

Expand Down Expand Up @@ -712,44 +748,11 @@ module.exports = (options = {}) => {
}
};

Resources[prefixed('LambdaScalingRole')] = {
Type: 'AWS::IAM::Role',
Properties: {
AssumeRolePolicyDocument: {
Statement: [
{
Effect: 'Allow',
Principal: { Service: ['lambda.amazonaws.com'] },
Action: ['sts:AssumeRole']
}
]
},
Policies: [{
PolicyName: 'CustomcfnScalingLambdaLogs',
PolicyDocument: {
Statement: [
{
Effect: 'Allow',
Action: [
'logs:*'
],
Resource: cf.join([
'arn:',
cf.partition,
':logs:*:*:*'
])
}
]
}
}]
}
};

Resources[prefixed('ScalingLambda')] = {
Type: 'AWS::Lambda::Function',
Properties: {
Handler: 'index.handler',
Role: cf.getAtt(prefixed('LambdaScalingRole'), 'Arn'),
Role: cf.getAtt(prefixed('Role'), 'Arn'),
Code: {
ZipFile: cf.sub(`
const response = require('./cfn-response');
Expand All @@ -775,7 +778,7 @@ module.exports = (options = {}) => {
Type: 'AWS::Lambda::Function',
Properties: {
Handler: 'index.handler',
Role: cf.getAtt(prefixed('LambdaTotalMessagesRole'), 'Arn'),
Role: cf.getAtt(prefixed('Role'), 'Arn'),
Timeout: 60,
Code: {
ZipFile: cf.sub(`
Expand Down Expand Up @@ -812,53 +815,6 @@ module.exports = (options = {}) => {
}
};

Resources[prefixed('LambdaTotalMessagesRole')] = {
Type: 'AWS::IAM::Role',
Properties: {
AssumeRolePolicyDocument: {
Statement: [
{
Effect: 'Allow',
Principal: { Service: ['lambda.amazonaws.com'] },
Action: ['sts:AssumeRole']
}
]
},
Policies: [{
PolicyName: 'LambdaTotalMessagesMetric',
PolicyDocument: {
Statement: [
{
Effect: 'Allow',
Action: [
'logs:*'
],
Resource: cf.join([
'arn:',
cf.partition,
':logs:*:*:*'
])
},
{
Effect: 'Allow',
Action: [
'cloudwatch:PutMetricData'
],
Resource: '*'
},
{
Effect: 'Allow',
Action: [
'sqs:GetQueueAttributes'
],
Resource: cf.getAtt(prefixed('Queue'), 'Arn')
}
]
}
}]
}
};

Resources[prefixed('TotalMessagesSchedule')] = {
Type: 'AWS::Events::Rule',
Properties: {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mapbox/watchbot",
"version": "9.0.1",
"version": "9.1.0",
"description": "",
"main": "index.js",
"engines": {
Expand Down