|
AWSTemplateFormatVersion: "2010-09-09"
|
|
Description: >
|
|
One IAM role letting GitHub Actions (canofytech/canofy-NSDL-data-extraction)
|
|
deploy this project's SAM stack, for ONE environment (TEST or PROD), via
|
|
OIDC -- no long-lived AWS keys stored in GitHub. Deploy this template once
|
|
per environment (same pattern as the main SAM template.yaml) so each
|
|
role's permissions are scoped to only that environment's own stack/
|
|
resource names -- the test pipeline can never touch prod-named resources
|
|
and vice versa. Assumes the account's GitHub OIDC provider
|
|
(token.actions.githubusercontent.com) already exists.
|
|
|
|
Trust is scoped via the OIDC token's "environment" claim (GitHub's own
|
|
recommended pattern for this), matching the workflow's `environment:` key
|
|
-- NOT a branch/ref pattern, which is easier to spoof via a
|
|
differently-named branch.
|
|
|
|
Parameters:
|
|
Environment:
|
|
Type: String
|
|
AllowedValues: [test, production]
|
|
Description: >
|
|
Which GitHub Environment (Settings -> Environments) this role trusts,
|
|
and which stack-name prefix its permissions are scoped to. Deploy
|
|
with "test" now; deploy again with "production" only once a prod
|
|
stack/environment actually exists.
|
|
GitHubOrg:
|
|
Type: String
|
|
Default: canofytech
|
|
GitHubRepo:
|
|
Type: String
|
|
Default: canofy-NSDL-data-extraction
|
|
StackNamePrefix:
|
|
Type: String
|
|
Description: >
|
|
The SAM stack name this role may manage, e.g.
|
|
nsdl-sez-boe-automation-test or nsdl-sez-boe-automation-prod. Must
|
|
match what --stack-name the corresponding workflow actually deploys.
|
|
|
|
Conditions:
|
|
IsTestEnvironment: !Equals [!Ref Environment, test]
|
|
|
|
Resources:
|
|
SamDeployPolicy:
|
|
Type: AWS::IAM::ManagedPolicy
|
|
Properties:
|
|
ManagedPolicyName: !Sub "nsdl-sez-boe-sam-deploy-policy-${Environment}"
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: CloudFormationAccess
|
|
Effect: Allow
|
|
Action:
|
|
- cloudformation:CreateStack
|
|
- cloudformation:UpdateStack
|
|
- cloudformation:DeleteStack
|
|
- cloudformation:DescribeStacks
|
|
- cloudformation:DescribeStackEvents
|
|
- cloudformation:DescribeStackResource
|
|
- cloudformation:DescribeStackResources
|
|
- cloudformation:GetTemplate
|
|
- cloudformation:GetTemplateSummary
|
|
- cloudformation:ListStackResources
|
|
- cloudformation:CreateChangeSet
|
|
- cloudformation:DescribeChangeSet
|
|
- cloudformation:ExecuteChangeSet
|
|
- cloudformation:DeleteChangeSet
|
|
- cloudformation:ListChangeSets
|
|
- cloudformation:ValidateTemplate
|
|
Resource:
|
|
- !Sub "arn:aws:cloudformation:*:${AWS::AccountId}:stack/${StackNamePrefix}/*"
|
|
|
|
# SAM templates use the AWS::Serverless-2016-10-31 transform;
|
|
# CloudFormation requires CreateChangeSet on the transform itself
|
|
# (an AWS-owned resource, no account ID) in addition to the stack.
|
|
- Sid: SamServerlessTransform
|
|
Effect: Allow
|
|
Action:
|
|
- cloudformation:CreateChangeSet
|
|
Resource:
|
|
- "arn:aws:cloudformation:*:aws:transform/Serverless-2016-10-31"
|
|
|
|
- Sid: SamCliManagedBucket
|
|
Effect: Allow
|
|
Action:
|
|
- s3:GetObject
|
|
- s3:PutObject
|
|
- s3:GetBucketLocation
|
|
- s3:ListBucket
|
|
Resource:
|
|
- "arn:aws:s3:::aws-sam-cli-managed-default*"
|
|
- "arn:aws:s3:::aws-sam-cli-managed-default*/*"
|
|
|
|
- Sid: ProjectS3Bucket
|
|
Effect: Allow
|
|
Action:
|
|
- s3:GetObject
|
|
- s3:PutObject
|
|
- s3:GetBucketLocation
|
|
- s3:ListBucket
|
|
- s3:PutBucketPolicy
|
|
- s3:GetBucketPolicy
|
|
- s3:PutEncryptionConfiguration
|
|
- s3:PutBucketPublicAccessBlock
|
|
- s3:PutLifecycleConfiguration
|
|
- s3:GetLifecycleConfiguration
|
|
- s3:CreateBucket
|
|
- s3:DeleteBucket
|
|
- s3:PutBucketTagging
|
|
Resource:
|
|
- !Sub "arn:aws:s3:::${StackNamePrefix}-*"
|
|
- !Sub "arn:aws:s3:::${StackNamePrefix}-*/*"
|
|
|
|
# ECR repo names carry a random hash suffix SAM generates at
|
|
# build time, and GetAuthorizationToken is only ever offered
|
|
# account-wide by AWS -- can't be scoped by environment.
|
|
- Sid: EcrAccess
|
|
Effect: Allow
|
|
Action:
|
|
- ecr:CreateRepository
|
|
- ecr:DescribeRepositories
|
|
- ecr:GetAuthorizationToken
|
|
- ecr:BatchCheckLayerAvailability
|
|
- ecr:GetDownloadUrlForLayer
|
|
- ecr:BatchGetImage
|
|
- ecr:PutImage
|
|
- ecr:InitiateLayerUpload
|
|
- ecr:UploadLayerPart
|
|
- ecr:CompleteLayerUpload
|
|
- ecr:SetRepositoryPolicy
|
|
- ecr:DeleteRepository
|
|
- ecr:TagResource
|
|
Resource: "*"
|
|
|
|
- Sid: LambdaAccess
|
|
Effect: Allow
|
|
Action:
|
|
- lambda:CreateFunction
|
|
- lambda:UpdateFunctionCode
|
|
- lambda:UpdateFunctionConfiguration
|
|
- lambda:GetFunction
|
|
- lambda:GetFunctionConfiguration
|
|
- lambda:DeleteFunction
|
|
- lambda:AddPermission
|
|
- lambda:RemovePermission
|
|
- lambda:GetPolicy
|
|
- lambda:ListVersionsByFunction
|
|
- lambda:PublishVersion
|
|
- lambda:PutFunctionEventInvokeConfig
|
|
- lambda:UpdateFunctionEventInvokeConfig
|
|
- lambda:GetFunctionEventInvokeConfig
|
|
- lambda:DeleteFunctionEventInvokeConfig
|
|
- lambda:TagResource
|
|
- lambda:UntagResource
|
|
- lambda:ListTags
|
|
Resource:
|
|
- !Sub "arn:aws:lambda:*:${AWS::AccountId}:function:${StackNamePrefix}-*"
|
|
# The watchdog is named explicitly in template.yaml
|
|
# (nsdl-boe-watchdog-<ENV>), not with the stack-name prefix.
|
|
- !Sub "arn:aws:lambda:*:${AWS::AccountId}:function:nsdl-boe-watchdog-*"
|
|
|
|
- Sid: DynamoDbAccess
|
|
Effect: Allow
|
|
Action:
|
|
- dynamodb:CreateTable
|
|
- dynamodb:UpdateTable
|
|
- dynamodb:DeleteTable
|
|
- dynamodb:DescribeTable
|
|
- dynamodb:DescribeTimeToLive
|
|
- dynamodb:UpdateTimeToLive
|
|
- dynamodb:TagResource
|
|
- dynamodb:ListTagsOfResource
|
|
Resource:
|
|
- !Sub "arn:aws:dynamodb:*:${AWS::AccountId}:table/${StackNamePrefix}-*"
|
|
|
|
# API Gateway's IAM model doesn't support scoping by resource name
|
|
# at creation time -- restricted to just the resource types this
|
|
# stack actually uses, not by environment.
|
|
- Sid: ApiGatewayAccess
|
|
Effect: Allow
|
|
Action:
|
|
- apigateway:*
|
|
Resource:
|
|
- "arn:aws:apigateway:*::/restapis*"
|
|
- "arn:aws:apigateway:*::/apikeys*"
|
|
- "arn:aws:apigateway:*::/usageplans*"
|
|
|
|
- Sid: SnsSqsAccess
|
|
Effect: Allow
|
|
Action:
|
|
- sns:CreateTopic
|
|
- sns:DeleteTopic
|
|
- sns:GetTopicAttributes
|
|
- sns:SetTopicAttributes
|
|
- sns:TagResource
|
|
- sqs:CreateQueue
|
|
- sqs:DeleteQueue
|
|
- sqs:GetQueueAttributes
|
|
- sqs:SetQueueAttributes
|
|
- sqs:TagQueue
|
|
Resource:
|
|
- !Sub "arn:aws:sns:*:${AWS::AccountId}:${StackNamePrefix}-*"
|
|
- !Sub "arn:aws:sqs:*:${AWS::AccountId}:${StackNamePrefix}-*"
|
|
# Topic/queue are named explicitly in template.yaml
|
|
# (nsdl-boe-alerts-<ENV>, nsdl-boe-phase-b-dlq-<ENV>).
|
|
- !Sub "arn:aws:sns:*:${AWS::AccountId}:nsdl-boe-alerts-*"
|
|
- !Sub "arn:aws:sqs:*:${AWS::AccountId}:nsdl-boe-phase-b-dlq-*"
|
|
|
|
- Sid: CloudWatchAlarms
|
|
Effect: Allow
|
|
Action:
|
|
- cloudwatch:PutMetricAlarm
|
|
- cloudwatch:DeleteAlarms
|
|
- cloudwatch:DescribeAlarms
|
|
Resource:
|
|
- !Sub "arn:aws:cloudwatch:*:${AWS::AccountId}:alarm:${StackNamePrefix}-*"
|
|
# Log group creation/retention can't be scoped ahead of creation.
|
|
- Sid: CloudWatchLogs
|
|
Effect: Allow
|
|
Action:
|
|
- logs:CreateLogGroup
|
|
- logs:DeleteLogGroup
|
|
- logs:PutRetentionPolicy
|
|
- logs:DescribeLogGroups
|
|
- logs:TagResource
|
|
Resource: "*"
|
|
|
|
- Sid: EventsSchedulerAccess
|
|
Effect: Allow
|
|
Action:
|
|
- events:PutRule
|
|
- events:DeleteRule
|
|
- events:DescribeRule
|
|
- events:PutTargets
|
|
- events:RemoveTargets
|
|
- events:ListTargetsByRule
|
|
- events:TagResource
|
|
Resource:
|
|
- !Sub "arn:aws:events:*:${AWS::AccountId}:rule/${StackNamePrefix}-*"
|
|
# SAM truncates the stack-name portion of schedule rule names
|
|
# to 25 characters (e.g. nsdl-sez-boe-automation-t-PhaseAFunction
|
|
# DailySchedu-<hash>), so the full-prefix pattern above never
|
|
# matches the real rules. Scoped per environment so the test role
|
|
# still cannot touch prod-named rules.
|
|
- !If
|
|
- IsTestEnvironment
|
|
- !Sub "arn:aws:events:*:${AWS::AccountId}:rule/nsdl-sez-boe-automation-t*"
|
|
- !Sub "arn:aws:events:*:${AWS::AccountId}:rule/nsdl-sez-boe-automation-p*"
|
|
|
|
# Deploy never reads the secret VALUE -- CloudFormation only
|
|
# needs to confirm the secret resource exists/is referenceable.
|
|
- Sid: SecretsManagerReadOnly
|
|
Effect: Allow
|
|
Action:
|
|
- secretsmanager:DescribeSecret
|
|
- secretsmanager:GetResourcePolicy
|
|
Resource:
|
|
- !Sub "arn:aws:secretsmanager:*:${AWS::AccountId}:secret:nsdl-boe-automation-secrets-*"
|
|
|
|
# The most sensitive statement here: SAM creates one execution
|
|
# role per Lambda function as part of this same stack. Scoped to
|
|
# this environment's own stack-name prefix -- cannot create or
|
|
# pass roles outside it, and cannot touch the other environment's
|
|
# roles even if this policy were somehow attached there.
|
|
- Sid: IamManageLambdaExecutionRoles
|
|
Effect: Allow
|
|
Action:
|
|
- iam:CreateRole
|
|
- iam:DeleteRole
|
|
- iam:GetRole
|
|
- iam:PutRolePolicy
|
|
- iam:DeleteRolePolicy
|
|
- iam:GetRolePolicy
|
|
- iam:AttachRolePolicy
|
|
- iam:DetachRolePolicy
|
|
- iam:TagRole
|
|
- iam:PassRole
|
|
Resource:
|
|
- !Sub "arn:aws:iam::${AWS::AccountId}:role/${StackNamePrefix}-*"
|
|
|
|
GitHubActionsDeployRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
RoleName: !Sub "nsdl-sez-boe-github-actions-${Environment}-deploy"
|
|
MaxSessionDuration: 3600
|
|
AssumeRolePolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com"
|
|
Action: sts:AssumeRoleWithWebIdentity
|
|
Condition:
|
|
StringEquals:
|
|
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
|
|
"token.actions.githubusercontent.com:sub": !Sub "repo:${GitHubOrg}/${GitHubRepo}:environment:${Environment}"
|
|
ManagedPolicyArns:
|
|
- !Ref SamDeployPolicy
|
|
|
|
Outputs:
|
|
RoleArn:
|
|
Value: !GetAtt GitHubActionsDeployRole.Arn
|
|
Description: >
|
|
Set as this repo's AWS_DEPLOY_ROLE_ARN (test) or
|
|
AWS_DEPLOY_ROLE_ARN_PROD (production) secret, under
|
|
Settings -> Environments -> <this Environment>.
|