@bowtie/sls
Version:
Serverless helpers & utilities
1,201 lines (1,140 loc) • 46 kB
YAML
# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
# docs.serverless.com
#
# Happy Coding!
service: sls-ci-${self:custom.serviceName}-root
plugins:
- serverless-offline
- serverless-webpack
# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
# frameworkVersion: "=X.X.X"
package:
exclude:
- tmp/**
- .git/**
- test/**
- services/**
include:
- "./services/${self:custom.serviceName}.yml"
provider:
name: aws
runtime: nodejs12.x
stage: dev
region: ${self:custom.region}
profile: ${opt:aws-profile, self:custom.service.aws.profile}
apiGateway:
restApiId:
Ref: ApiGatewayRestApi
restApiRootResourceId:
Fn::GetAtt: ApiGatewayRestApi.RootResourceId
# restApiResources:
# users: { 'Fn::ImportValue': 'postsapi-${opt:stage}-ApiGatewayResourceUsers' }
# users/me:
# Fn::ImportValue: 'postsapi-${opt:stage}-ApiGatewayResourceUsersMe'
# [HIGH] TODO: Clean this up, don't default to full access to Dynamo
iamRoleStatements:
- Effect: "Allow"
Action:
- "dynamodb:*"
Resource: "*"
environment:
SLS_BASE_URL: { "Fn::Join" : ["", [" https://", { "Ref" : "ApiGatewayRestApi" }, ".execute-api.${self:custom.region}.amazonaws.com/${self:provider.stage}" ] ] }
SLS_API_BASE: ${self:custom.apiBaseUrl}
SLS_STAGE: ${self:provider.stage}
CTX_SECURE: sec
ECR_REPO_NAME: ${self:custom.ecrRepoName}
SERVICE_NAME: ${self:custom.serviceName}
# TODO: Add github secret, token
BUILD_BUCKET_NAME: ${self:custom.buildBucketName}
SITE_BUCKET_NAME: ${self:custom.siteBucketName}
ASSET_BUCKET_NAME: ${self:custom.assetBucketName}
SECURE_BUCKET_NAME: ${self:custom.secureBucketName}
BUILDS_TABLE_NAME: ${self:custom.buildsTableName}
DEPLOYS_TABLE_NAME: ${self:custom.deploysTableName}
AUDITS_TABLE_NAME: ${self:custom.auditsTableName}
DOCUMENTS_TABLE_NAME: ${self:custom.documentsTableName}
SUBMISSIONS_TABLE_NAME: ${self:custom.submissionsTableName}
BUILD_PROJECT_NAME: ${self:custom.buildProjectName}
RECAPTCHA_SECRET_KEY: ${self:custom.recaptchaSecretKey}
# TODO: Add slack token for app/commands/etc
SLACK_WEBHOOK: ${self:custom.service.slack.webhook}
SLACK_CHANNEL: ${self:custom.service.slack.channel}
SLACK_USERNAME: ${self:custom.service.slack.username}
SLACK_ICON_EMOJI: ${self:custom.service.slack.icon_emoji}
SLACK_ICON_URL: ${self:custom.service.slack.icon_url}
SEND_EMAIL_FROM: ${self:custom.sendEmailFromAddress}
SEND_EMAIL_CONF: ${self:custom.sendEmailConfName}
PUBNUB_PUBLISH_KEY: ${self:custom.service.pubnub.publish_key}
PUBNUB_SUBSCRIBE_KEY: ${self:custom.service.pubnub.subscribe_key}
UPDATE_ROLE_ARN: { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":role/${self:custom.serviceUpdateRoleName}" ] ] }
NOTIFY_SNS_ARN: { "Fn::Join" : ["", ["arn:aws:sns:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":${self:custom.stackChangeTopic}" ] ] }
custom:
serverless-offline:
port: 5000
prefix: dev
stage: dev
service: ${file(./services/${opt:service}.yml):${opt:service}}
apiVersion: v1
apiBaseUrl: "api/${self:custom.apiVersion}"
region: ${opt:region, self:custom.service.aws.region}
namespace: "${self:service}-${self:custom.region}"
serviceName: ${opt:service}
apiGatewayName: "${self:custom.namespace}-api"
ecrRepoName: ${self:custom.namespace}-repo
buildBucketName: ${self:custom.namespace}-build-bucket
siteBucketName: ${self:custom.namespace}-site
assetBucketName: ${self:custom.namespace}-assets
secureBucketName: ${self:custom.namespace}-secure
buildsTableName: "${self:custom.namespace}-builds"
deploysTableName: "${self:custom.namespace}-deploys"
auditsTableName: "${self:custom.namespace}-audits"
documentsTableName: "${self:custom.namespace}-documents"
submissionsTableName: "${self:custom.namespace}-submissions"
buildProjectName: "${self:custom.namespace}-build-project"
# buildProjectSource: ${self:custom.service.source}
buildProjectSourceType: ${self:custom.service.source.type, 'GITHUB'}
buildProjectSourceBase: ${self:custom.service.source.base, 'https://github.com'}
sendEmailFromAddress: ${self:custom.service.email}
stackChangeTopic: "${self:custom.namespace}-stack-change"
buildChangeTopic: "${self:custom.namespace}-build-change"
buildProjectRoleName: "${self:custom.namespace}-build-project-role"
startBuildRoleName: "${self:custom.namespace}-start-build-role"
sendEmailRoleName: "${self:custom.namespace}-send-email-role"
sendEmailConfName: "${self:custom.namespace}-send-email-conf"
# apiHandlerRoleName: "${self:custom.namespace}-api-handler-role"
notifySlackRoleName: "${self:custom.namespace}-notify-slack-role"
stackUpdateRoleName: "${self:custom.namespace}-update-stack-role"
serviceUpdateRoleName: "${self:custom.namespace}-update-service-role"
recaptchaSecretKey: "${self:custom.service.recaptcha_secret_key}"
functions:
# email:
# handler: handler.sendEmail
# role: sendEmailRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/email
# method: post
# cors: true
# upload:
# handler: handler.s3Upload
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/upload
# method: post
# cors: true
# info:
# handler: handler.info
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/info
# method: get
# cors: true
# builds-tags:
# handler: handler.builds_tags
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/builds/tags
# method: get
# cors: true
# deploys-stacks:
# handler: handler.deploys_stacks
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/deploys/stacks
# method: get
# cors: true
# builds-deploy:
# handler: handler.builds_deploy
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/builds/{id}/deploy/{stack}
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# stack: true
# builds-index:
# handler: handler.builds_index
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/builds
# method: get
# cors: true
# builds-show:
# handler: handler.builds_show
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/builds/{id}
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# builds-logs:
# handler: handler.builds_logs
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/builds/{id}/logs
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# builds-create:
# handler: handler.builds_create
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/builds
# method: post
# cors: true
# builds-update:
# handler: handler.builds_update
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/builds/{id}
# method: put
# cors: true
# request:
# parameters:
# paths:
# id: true
# builds-destroy:
# handler: handler.builds_destroy
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/builds/{id}
# method: delete
# cors: true
# request:
# parameters:
# paths:
# id: true
# deploys-index:
# handler: handler.deploys_index
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/deploys
# method: get
# cors: true
# deploys-show:
# handler: handler.deploys_show
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/deploys/{id}
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# deploys-create:
# handler: handler.deploys_create
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/deploys
# method: post
# cors: true
# deploys-update:
# handler: handler.deploys_update
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/deploys/{id}
# method: put
# cors: true
# request:
# parameters:
# paths:
# id: true
# deploys-destroy:
# handler: handler.deploys_destroy
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/deploys/{id}
# method: delete
# cors: true
# request:
# parameters:
# paths:
# id: true
# verify-recaptcha:
# handler: handler.verifyRecaptcha
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/recaptcha
# method: get
# cors: true
# submissions-index:
# handler: handler.submissions_index
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/submissions
# method: get
# cors: true
# submissions-show:
# handler: handler.submissions_show
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/submissions/{id}
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# submissions-create:
# handler: handler.submissions_create
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/submissions
# method: post
# cors: true
# submissions-update:
# handler: handler.submissions_update
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/submissions/{id}
# method: put
# cors: true
# request:
# parameters:
# paths:
# id: true
# submissions-download:
# handler: handler.submissions_download
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/submissions/{id}/download
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# documents-index:
# handler: handler.documents_index
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/documents
# method: get
# cors: true
# documents-audits:
# handler: handler.documents_audits
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/documents/{id}/audits
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# documents-download:
# handler: handler.documents_download
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/documents/{id}/download
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# documents-show:
# handler: handler.documents_show
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/documents/{id}
# method: get
# cors: true
# request:
# parameters:
# paths:
# id: true
# documents-create:
# handler: handler.documents_create
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/documents
# method: post
# cors: true
# documents-update:
# handler: handler.documents_update
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/documents/{id}
# method: put
# cors: true
# request:
# parameters:
# paths:
# id: true
# documents-destroy:
# handler: handler.documents_destroy
# role: apiHandlerRole
# events:
# - http:
# path: ${self:custom.apiBaseUrl}/documents/{id}
# method: delete
# cors: true
# request:
# parameters:
# paths:
# id: true
stack-change:
handler: handler.stackChange
role: notifySlackRole
events:
- sns: ${self:custom.stackChangeTopic}
build-change:
handler: handler.buildChange
role: updateStackRole
events:
- cloudwatchEvent:
description: 'CloudWatch Event triggered on a Code Build Project'
event:
source:
- "aws.codebuild"
detail-type:
- "CodeBuild Build State Change"
detail:
build-status:
- IN_PROGRESS
- SUCCEEDED
- FAILED
- STOPPED
project-name:
- ${self:custom.buildProjectName}
bitbucket-webhook:
handler: handler.bitbucketWebhook
role: startBuildRole
events:
- http:
path: bitbucket/webhook
method: post
github-webhook:
handler: handler.githubWebhook
role: startBuildRole
events:
- http:
path: github/webhook
method: post
# TODO: Add slack command & action function
resources:
Conditions:
HasTargetEcs: { "Fn::Equals" : ["${self:custom.service.target}", "ecs"] }
HasTargetS3: { "Fn::Equals" : ["${self:custom.service.target}", "s3"] }
# HasEmailFrom: { "Fn::Not": [ "Fn::Equals": ["${self:custom.sendEmailFromAddress}", ""] ] }
Resources:
# Rest API
ApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: ${self:custom.apiGatewayName}
Description: "${self:custom.serviceName} API Gateway"
# TODO: Finish support for s3 static site? CloudFront? Or remove this?
siteBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: ${self:custom.siteBucketName}
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
# DeletionPolicy: Retain
siteBucketPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: ${self:custom.siteBucketName}
PolicyDocument:
Id: PublicS3WebsitePolicy
Version: '2012-10-17'
Statement:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: '*'
Action: 's3:GetObject'
Resource: 'arn:aws:s3:::${self:custom.siteBucketName}/*'
assetBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: ${self:custom.assetBucketName}
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
# DeletionPolicy: Retain
CorsConfiguration:
CorsRules:
- MaxAge: 300
# ExposedHeaders: ['*']
AllowedHeaders: ['*']
AllowedOrigins: ['*']
AllowedMethods:
- HEAD
- GET
- PUT
assetBucketPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: ${self:custom.assetBucketName}
PolicyDocument:
Id: PublicS3WebsitePolicy
Version: '2012-10-17'
Statement:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: '*'
Action: 's3:GetObject'
Resource: 'arn:aws:s3:::${self:custom.assetBucketName}/*'
buildBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.buildBucketName}
secureBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.secureBucketName}
AccessControl: Private
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
CorsConfiguration:
CorsRules:
- MaxAge: 300
# ExposedHeaders: ['*']
AllowedHeaders: ['*']
AllowedOrigins: ['*']
AllowedMethods:
- HEAD
- GET
- PUT
ecrRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: ${self:custom.ecrRepoName}
buildsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:custom.buildsTableName}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: 'S'
# - AttributeName: build_timestamp
# AttributeType: 'N'
# [HIGH] TODO: Use build_number? or commit SHA or another col for keys?
KeySchema:
- AttributeName: id
KeyType: HASH
# - AttributeName: build_timestamp
# KeyType: RANGE
deploysTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:custom.deploysTableName}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: 'S'
# - AttributeName: deploy_timestamp
# AttributeType: 'N'
# [HIGH] TODO: Use build_number? or commit SHA or another col for keys?
KeySchema:
- AttributeName: id
KeyType: HASH
# - AttributeName: deploy_timestamp
# KeyType: RANGE
# documentsTable:
# Type: AWS::DynamoDB::Table
# Properties:
# TableName: ${self:custom.documentsTableName}
# BillingMode: PAY_PER_REQUEST
# AttributeDefinitions:
# - AttributeName: id
# AttributeType: 'S'
# - AttributeName: timestamp
# AttributeType: 'N'
# KeySchema:
# - AttributeName: id
# KeyType: HASH
# - AttributeName: timestamp
# KeyType: RANGE
# sendEmailConfigSet:
# Type: AWS::SES::ConfigurationSet
# Properties:
# Name: "${self:custom.sendEmailConfName}"
sendEmailRole:
Type: AWS::IAM::Role
Properties:
RoleName: ${self:custom.sendEmailRoleName}
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: sendEmail
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: "Allow"
Action:
- "ses:SendEmail"
Resource:
- "*"
- Effect: "Allow"
Action:
- "logs:CreateLogStream"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*" ] ] }
- Effect: "Allow"
Action:
- "logs:PutLogEvents"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*:*" ] ] }
# apiHandlerRole:
# Type: AWS::IAM::Role
# Properties:
# RoleName: ${self:custom.apiHandlerRoleName}
# AssumeRolePolicyDocument:
# Version: '2012-10-17'
# Statement:
# - Effect: Allow
# Principal:
# Service:
# - lambda.amazonaws.com
# Action: sts:AssumeRole
# Policies:
# - PolicyName: notifySlack
# PolicyDocument:
# Version: '2012-10-17'
# Statement:
# # [HIGH] TODO: Refactor policies & access => min required perms
# - Effect: "Allow"
# Action:
# - "s3:*"
# Resource:
# - 'arn:aws:s3:::${self:custom.secureBucketName}'
# - 'arn:aws:s3:::${self:custom.secureBucketName}/*'
# - 'arn:aws:s3:::${self:custom.buildBucketName}'
# - 'arn:aws:s3:::${self:custom.buildBucketName}/*'
# - 'arn:aws:s3:::${self:custom.siteBucketName}'
# - 'arn:aws:s3:::${self:custom.siteBucketName}/*'
# - 'arn:aws:s3:::${self:custom.assetBucketName}'
# - 'arn:aws:s3:::${self:custom.assetBucketName}/*'
# - Effect: "Allow"
# Action:
# - "logs:CreateLogStream"
# Resource:
# - { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*" ] ] }
# - Effect: "Allow"
# Action:
# - "logs:PutLogEvents"
# Resource:
# - { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*:*" ] ] }
# - Effect: Allow
# Action:
# - dynamodb:*
# Resource:
# - { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.buildsTableName}"]]}
# - { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.deploysTableName}"]]}
# - { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.documentsTableName}"]]}
# - { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.submissionsTableName}"]]}
# - { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.auditsTableName}"]]}
# - Effect: Allow
# Action:
# - codebuild:BatchGetBuilds
# Resource:
# - { "Fn::Join" : ["", ["arn:aws:codebuild:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":project/${self:custom.buildProjectName}"]]}
# - Effect: Allow
# Action:
# - "logs:GetLogEvents"
# Resource:
# - { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/codebuild/${self:custom.buildProjectName}:log-stream:*"]]}
# - Effect: "Allow"
# Action:
# - "cloudformation:UpdateStack"
# - "cloudformation:DescribeStacks"
# Resource:
# # - "*"
# # [HIGH] TODO: Why was this failing perms for example-app?
# - { "Fn::Join" : ["", ["arn:aws:cloudformation:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":stack/${self:custom.serviceName}-*" ] ] }
# - Effect: "Allow"
# Action:
# - "iam:PassRole"
# Resource:
# - { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":role/${self:custom.serviceUpdateRoleName}" ] ] }
buildProject:
Type: AWS::CodeBuild::Project
DependsOn: buildProjectRole
Properties:
Name: ${self:custom.buildProjectName}
ServiceRole: ${self:custom.buildProjectRoleName}
Cache:
Type: S3
Location: ${self:custom.buildBucketName}
Artifacts:
Type: no_artifacts
Source:
# [HIGH] TODO: Dynamic source? GH vs BB
Location: ${self:custom.service.source.base}/${self:custom.service.source.repo}.git
Type: "${self:custom.service.source.type}"
Auth:
Type: OAUTH
Environment:
# [HIGH] TODO: Configure this
ComputeType: "BUILD_GENERAL1_SMALL"
PrivilegedMode: true
Image: "bowtie/docker-builder:v3"
Type: "LINUX_CONTAINER"
EnvironmentVariables:
- Name: CI
Value: 'true'
- Name: AWS_REGION
Value: ${self:custom.region}
- Name: AWS_ACCOUNT
Value: { "Ref" : "AWS::AccountId" }
- Name: REPO_SLUG
Value: ${self:custom.ecrRepoName}
- Name: AWS_BUCKET_NAME
Value: ${self:custom.buildBucketName}
- Name: AWS_SITE_BUCKET_NAME
Value: ${self:custom.siteBucketName}
- Name: IAM_ROLE_NAME
Value: ${self:custom.buildProjectRoleName}
- Name: GIT_REPO_NAME
Value: ${self:custom.service.source.repo, ""}
- Name: "${self:custom.service.source.type}_REPO"
Value: ${self:custom.service.source.repo, ""}
- Name: AIRBRAKE_PROJECT_ID
Value: ${self:custom.service.airbrake.id, ""}
- Name: AIRBRAKE_PROJECT_KEY
Value: ${self:custom.service.airbrake.key, ""}
buildProjectRole:
Type: AWS::IAM::Role
Properties:
RoleName: ${self:custom.buildProjectRoleName}
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: "Allow"
Principal:
Service:
- codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: buildProject
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:*
Resource:
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.buildsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.deploysTableName}"]]}
- Effect: "Allow"
Action:
- "s3:*"
Resource:
- 'arn:aws:s3:::${self:custom.buildBucketName}'
- 'arn:aws:s3:::${self:custom.buildBucketName}/*'
- 'arn:aws:s3:::${self:custom.siteBucketName}'
- 'arn:aws:s3:::${self:custom.siteBucketName}/*'
- Effect: "Allow"
Action:
- "ecr:InitiateLayerUpload"
- "ecr:UploadLayerPart"
- "ecr:CompleteLayerUpload"
- "ecr:GetDownloadUrlForLayer"
- "ecr:BatchGetImage"
- "ecr:BatchCheckLayerAvailability"
- "ecr:PutImage"
- "ecr:GetAuthorizationToken"
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "cloudfront:ListInvalidations"
- "cloudfront:GetInvalidation"
- "cloudfront:CreateInvalidation"
# [HIGH] TODO: Lock down this resource definition
Resource: "*"
startBuildRole:
Type: AWS::IAM::Role
Properties:
RoleName: ${self:custom.startBuildRoleName}
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: notifySlack
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: "Allow"
Action:
- "ecr:BatchGetImage"
- "ecr:DescribeImages"
- "ecr:PutImage"
Resource: "*"
- Effect: "Allow"
Action:
- "logs:CreateLogStream"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*" ] ] }
- Effect: "Allow"
Action:
- "logs:PutLogEvents"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*:*" ] ] }
- Effect: "Allow"
Action:
- "codebuild:StartBuild"
- "codebuild:BatchGetBuilds"
Resource:
- { "Fn::Join" : ["", ["arn:aws:codebuild:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":project/${self:custom.buildProjectName}"]]}
- Effect: Allow
Action:
- dynamodb:*
Resource:
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.buildsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.deploysTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.documentsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.submissionsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.auditsTableName}"]]}
- Effect: "Allow"
Action:
- "logs:GetLogEvents"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/codebuild/${self:custom.buildProjectName}:log-stream:*" ] ] }
- Effect: "Allow"
Action:
- "cloudformation:UpdateStack"
- "cloudformation:DescribeStacks"
Resource:
- { "Fn::Join" : ["", ["arn:aws:cloudformation:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":stack/${self:custom.serviceName}-*" ] ] }
- Effect: "Allow"
Action:
- "iam:PassRole"
Resource:
- { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":role/${self:custom.serviceUpdateRoleName}" ] ] }
notifySlackRole:
Type: AWS::IAM::Role
Properties:
RoleName: ${self:custom.notifySlackRoleName}
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: notifySlack
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:*
Resource:
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.buildsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.deploysTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.documentsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.submissionsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.auditsTableName}"]]}
- Effect: "Allow"
Action:
- "logs:CreateLogStream"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*" ] ] }
- Effect: "Allow"
Action:
- "logs:PutLogEvents"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*:*" ] ] }
- Effect: "Allow"
Action:
- "ecs:ListTaskDefinitions"
- "cloudformation:DescribeStacks"
Resource: "*"
- Effect: "Allow"
Action:
- "SNS:Publish"
Resource:
- { "Fn::Join" : ["", ["arn:aws:sns:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":${self:custom.stackChangeTopic}" ] ] }
- Effect: "Allow"
Action:
- "cloudwatch:DescribeAlarms"
- "cloudwatch:PutMetricAlarm"
Resource:
- { "Fn::Join" : ["", ["arn:aws:cloudwatch:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":*/${self:custom.serviceName}*" ] ] }
- Effect: "Allow"
Action:
- "ecs:List*"
- "ecs:RunTask"
- "ecs:Describe*"
- "ecs:RegisterTaskDefinition"
- "ecs:DeregisterTaskDefinition"
- "ecs:UpdateService"
Resource:
- { "Fn::Join" : ["", ["arn:aws:ecs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":*/${self:custom.serviceName}*" ] ] }
- Effect: "Allow"
Action:
- "iam:AttachRolePolicy"
- "iam:CreateRole"
- "iam:GetPolicy"
- "iam:GetPolicyVersion"
- "iam:GetRole"
- "iam:PassRole"
- "iam:ListAttachedRolePolicies"
- "iam:ListRoles"
- "iam:ListGroups"
- "iam:ListUsers"
Resource:
- { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":user/${self:custom.serviceName}*" ] ] }
- { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":role/${self:custom.serviceName}*" ] ] }
- { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":group/${self:custom.serviceName}*" ] ] }
- { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":policy/${self:custom.serviceName}*" ] ] }
- { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":instance-profile/${self:custom.serviceName}*" ] ] }
updateServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: ${self:custom.serviceUpdateRoleName}
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- cloudformation.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: updateService
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:*
Resource:
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.buildsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.deploysTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.documentsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.submissionsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.auditsTableName}"]]}
- Effect: "Allow"
Action:
- "SNS:Publish"
Resource:
- { "Fn::Join" : ["", ["arn:aws:sns:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":${self:custom.stackChangeTopic}" ] ] }
- Effect: "Allow"
Action:
- "application-autoscaling:Describe*"
- "application-autoscaling:PutScalingPolicy"
- "application-autoscaling:DeleteScalingPolicy"
- "application-autoscaling:RegisterScalableTarget"
- "cloudwatch:DescribeAlarms"
- "cloudwatch:PutMetricAlarm"
- "ecs:List*"
- "ecs:Describe*"
- "ecs:RegisterTaskDefinition"
- "ecs:DeregisterTaskDefinition"
- "ecs:UpdateService"
- "iam:AttachRolePolicy"
- "iam:CreateRole"
- "iam:GetPolicy"
- "iam:GetPolicyVersion"
- "iam:GetRole"
- "iam:PassRole"
- "iam:ListAttachedRolePolicies"
- "iam:ListRoles"
- "iam:ListGroups"
- "iam:ListUsers"
Resource: "*"
updateStackRole:
Type: AWS::IAM::Role
Properties:
RoleName: ${self:custom.stackUpdateRoleName}
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: updateStack
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:*
Resource:
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.buildsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.deploysTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.documentsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.submissionsTableName}"]]}
- { "Fn::Join" : ["", ["arn:aws:dynamodb:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":table/${self:custom.auditsTableName}"]]}
- Effect: "Allow"
Action:
- "ecr:BatchGetImage"
Resource: "*"
- Effect: "Allow"
Action:
- "logs:CreateLogStream"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*" ] ] }
- Effect: "Allow"
Action:
- "logs:PutLogEvents"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/lambda/${self:service}-${self:provider.stage}-*:*:*" ] ] }
- Effect: "Allow"
Action:
- "logs:GetLogEvents"
Resource:
- { "Fn::Join" : ["", ["arn:aws:logs:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":log-group:/aws/codebuild/${self:custom.buildProjectName}:log-stream:*" ] ] }
- Effect: "Allow"
Action:
- "cloudformation:UpdateStack"
- "cloudformation:DescribeStacks"
Resource:
# - "*"
# [HIGH] TODO: Why was this failing perms for example-app?
- { "Fn::Join" : ["", ["arn:aws:cloudformation:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":stack/${self:custom.serviceName}-*" ] ] }
- Effect: "Allow"
Action:
- "iam:PassRole"
Resource:
- { "Fn::Join" : ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":role/${self:custom.serviceUpdateRoleName}" ] ] }
- Effect: "Allow"
Action:
- "ecr:BatchGetImage"
- "ecr:DescribeImages"
Resource: "*"
Outputs:
# RestApi resource ID (e.g. ei829oe)
restApiId:
Value:
Ref: ApiGatewayRestApi
Export:
Name: "${self:custom.serviceName}-restApiId"
# RestApi Root Resource (the implicit '/' path)
restApiRootResourceId:
Value:
Fn::GetAtt: ApiGatewayRestApi.RootResourceId
Export:
Name: "${self:custom.serviceName}-restApiRootResourceId"
buildBucket:
Description: 'buildBucket value'
Value: { "Ref": "buildBucket" }
Export:
Name: "${self:custom.serviceName}-buildBucket"
siteBucket:
Description: 'siteBucket value'
Value: { "Ref": "siteBucket" }
Export:
Name: "${self:custom.serviceName}-siteBucket"
buildProject:
Description: 'buildProject value'
Value: { "Ref": "buildProject" }
Export:
Name: "${self:custom.serviceName}-buildProject"
buildProjectRole:
Description: 'buildProjectRole value'
Value: { "Ref": "buildProjectRole" }
Export:
Name: "${self:custom.serviceName}-buildProjectRole"
buildsTable:
Description: 'buildsTable value'
Value: { "Ref": "buildsTable" }
Export:
Name: "${self:custom.serviceName}-buildsTable"
deploysTable:
Description: 'deploysTable value'
Value: { "Ref": "deploysTable" }
Export:
Name: "${self:custom.serviceName}-deploysTable"
ecrRepository:
Description: 'ecrRepository value'
Value: { "Ref": "ecrRepository" }
Export:
Name: "${self:custom.serviceName}-ecrRepository"
notifySlackRole:
Description: 'notifySlackRole value'
Value: { "Ref": "notifySlackRole" }
Export:
Name: "${self:custom.serviceName}-notifySlackRole"
startBuildRole:
Description: 'startBuildRole value'
Value: { "Ref": "startBuildRole" }
Export:
Name: "${self:custom.serviceName}-startBuildRole"
updateServiceRole:
Description: 'updateServiceRole value'
Value: { "Ref": "updateServiceRole" }
Export:
Name: "${self:custom.serviceName}-updateServiceRole"
updateStackRole:
Description: 'updateStackRole value'
Value: { "Ref": "updateStackRole" }
Export:
Name: "${self:custom.serviceName}-updateStackRole"
notifySnsTopic:
Description: 'notifySnsTopic value'
Value: { "Fn::Join" : ["", ["arn:aws:sns:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":${self:custom.stackChangeTopic}" ] ] }
Export:
Name: "${self:custom.serviceName}-notifySnsTopic"