How to create a private AWS Api Gateway using cloudformation? - amazon-web-services

I am trying to create an AWS API Gateway of PRIVATE type,
This requires a resource policy, which I have as I'm able to create the gateway from the AWS Console,
I wanted to know how I could add the resource policy via the CF template -
Following is the swagger definition of the resource policy -
x-amazon-apigateway-policy:
Version: "2012-10-17"
Statement:
- Effect: "Deny"
Principal: "*"
Action: "execute-api:Invoke"
Resource: "arn:aws:execute-api:us-east-1:awsAccountId:xxxx/*/*/*"
Condition:
StringNotEquals:
aws:sourceVpc: "vpc-xxxxx"
- Effect: "Allow"
Principal: "*"
Action: "execute-api:Invoke"
Resource: "arn:aws:execute-api:us-east-1:xxxx:xxxx/*/*/*"
How can I configure it in the CF template -
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Description: G2G Api Template Stack
Resources:
g2gPrivate:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: 'private-gw'
EndpointConfiguration:
Types:
- PRIVATE
Reference -
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html
https://medium.com/#cathmgarcia/conditional-resource-policy-on-aws-sam-with-inline-swagger-816ce946dbb

You need to supply the policy under a key (called Policy at the same level as Name.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-policy
This needs to be supplied in the JSON format.
Something like...
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Description: G2G Api Template Stack
Resources:
g2gPrivate:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: 'private-gw'
EndpointConfiguration:
Types:
- PRIVATE
Policy: !Sub |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:${AWS::AccountId}:*/*/*/*",
"Condition": {
"StringNotEquals": {
"aws:sourceVpc": "vpc-xxxxx"
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:${AWS::AccountId}:*/*/*/*"
}
]
}

Related

Cloudformation update stack policy condition on stack name or environment

I have a policy attached to my CloudFormation stack:
{
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"Update:*"
],
"Resource": "*"
},
{
"Effect": "Deny",
"Principal": "*",
"Resource": "*",
"Action": [
"Update:Replace",
"Update:Delete"
],
"Condition": {
"StringEquals": {
"ResourceType": [
"AWS::SNS::Topic",
"AWS::SQS::Queue"
]
}
}
}
]
}
The policy prevents accidental deleting of SNS/SQS resources. I want to make the policy more liberal in a dev environment. How do I disable the Deny statement conditionally, for example, if my CF (cloudformation) stack name is my-app-dev or the CF stack has a tag STAGE equal to dev?
Btw the policy is generated by the serverless framework, so I will have to write it in serverless.yml
This can be done by using the environment variables of the serverless framework.
serverless.yml
service: sample
provider:
name: aws
stage: ${opt:stage,"dev"}
region: ap-northeast-1
custom:
policyChange:
prd: Deny
dev: Allow
resources:
- ${file(iam.yml)}
iam.yml
Resources:
SampleRole:
Type: AWS::IAM::Role
Properties:
RoleName: SampleRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: SamplePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: '${self:custom.policyChange.${self:provider.stage}}'
Resource: "*"
Action:
- sqs:*

AWS Service-linked role for DynamoDB tables and global secondary indexes autoscaling

As per the documentation here, there is a service linked role for dynamodb auto scaling - AWSServiceRoleForApplicationAutoScaling_DynamoDBTable.
The role permissions policy allows Application Auto Scaling to complete the following actions on all resources:
Action: dynamodb:DescribeTable
Action: dynamodb:UpdateTable
Action: cloudwatch:DeleteAlarms
Action: cloudwatch:DescribeAlarms
Action: cloudwatch:PutMetricAlarm
which translates to (from here),
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:DescribeTable",
"dynamodb:UpdateTable",
"cloudwatch:DeleteAlarms",
"cloudwatch:DescribeAlarms",
"cloudwatch:PutMetricAlarm"
],
"Resource": "*"
}
]
}
So for example, when the policy is used like below,
TableLiveProductsReadScalableTarget:
Type: 'AWS::ApplicationAutoScaling::ScalableTarget'
Properties:
MaxCapacity: !Ref TableLiveProductsReadMaxCap
MinCapacity: !Ref TableLiveProductsReadMinCap
ResourceId: !Sub "table/${TableLiveProducts}"
RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/dynamodb.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_DynamoDBTable
ScalableDimension: 'dynamodb:table:ReadCapacityUnits'
ServiceNamespace: dynamodb
from a security perspective is it okay to assume that, since the role can only be assumed by dynamodb.application-autoscaling.amazonaws.com no problem giving permission to update ALL the tables, delete ALL the alarms etc.?
What is the rationale behind asking for such a wildcard permission here (as well as in many AWS built service linked roles)?
They are meant to be most generic for your account. So one role covers every table for you. Since principle is dynamodb.application-autoscaling.amazonaws.com no other service nor IAM user/role can use these permissions.
You can provide your own role instead, with more granual setup. So to limit permissions to only one table you can do:
MyDynamoDBRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal: {'Service': ['dynamodb.application-autoscaling.amazonaws.com']}
Action: ['sts:AssumeRole']
Path: '/'
Policies:
- PolicyName: DynamoDBScaling
PolicyDocument: !Sub |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:DescribeTable",
"dynamodb:UpdateTable"
],
"Resource": "${TableLiveProducts.Arn}"
},
{
"Effect": "Allow",
"Action": [
"cloudwatch:PutMetricAlarm",
"cloudwatch:DescribeAlarms",
"cloudwatch:DeleteAlarms"
],
"Resource": "*"
}
]
}
TableLiveProductsReadScalableTarget:
Type: 'AWS::ApplicationAutoScaling::ScalableTarget'
Properties:
MaxCapacity: !Ref TableLiveProductsReadMaxCap
MinCapacity: !Ref TableLiveProductsReadMinCap
ResourceId: !Sub "table/${TableLiveProducts}"
RoleARN: !GetAtt MyDynamoDBRole.Arn
ScalableDimension: 'dynamodb:table:ReadCapacityUnits'
ServiceNamespace: dynamodb

CloudFormation API Gateway Lambda integration request not getting right permissions?

I have a CloudFormation stack template that creates an API Gateway resource where the method is of Type: LAMBDA_PROXY. It initially works fine for accessing the root domain, for example https://28af295f439b5f0aef7c7805864ba3981f282e1e.guacchain.com/ - but when I try accessing https://28af295f439b5f0aef7c7805864ba3981f282e1e.guacchain.com/about the network request gives back status code 500 and response {"message": "Internal server error"}
The generated Lambda function has this as its Resource-based Policy:
{
"Version": "2012-10-17",
"Id": "default",
"Statement": [
{
"Sid": "stack-28af295f439b5f0aef7c7805864ba3981f282e1e-lambdaApiGatewayInvoke-128TRSSUE8WDQ",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:378688096774:function:lambda-28af295f439b5f0aef7c7805864ba3981f282e1e",
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:us-east-1:378688096774:bccwb0kvvd/*/*/*"
}
}
}
]
}
When going to the API Gateway Integration Request page and simply editing the Lambda Function field (cutting the existing value then pasting it back, then hitting the checkmark), I get this "Add permission" popup:
After clicking OK and then refreshing the Lambda console page, its Resource-base Policy updates to include two seemingly duplicate statements (only difference being the Sid field):
{
"Version": "2012-10-17",
"Id": "default",
"Statement": [
{
"Sid": "stack-28af295f439b5f0aef7c7805864ba3981f282e1e-lambdaApiGatewayInvoke-128TRSSUE8WDQ",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:378688096774:function:lambda-28af295f439b5f0aef7c7805864ba3981f282e1e",
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:us-east-1:378688096774:bccwb0kvvd/*/*/*"
}
}
},
{
"Sid": "d6d795d4-8461-4774-bd6e-ae8d8ea3bcee",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:378688096774:function:lambda-28af295f439b5f0aef7c7805864ba3981f282e1e",
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:us-east-1:378688096774:bccwb0kvvd/*/*/*"
}
}
}
]
}
After deploying the API Gateway and waiting about a minute, https://28af295f439b5f0aef7c7805864ba3981f282e1e.guacchain.com/about finally becomes accessible. So my question is, what's inadequate with the original Lambda Resource-based Policy that prevents all requests except for the / one to be accessed on the domain?
One subtle note I want to point out is that after cutting the Lambda function name and pasting it on the Integration Request page, it doesn't show as an autocomplete option, while others do.
This is the lambdaIAMRole I have defined in the CloudFormation stack:
lambdaIAMRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Policies:
- PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Effect: Allow
Resource:
- !Sub >-
arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${lambdaFunctionName}:*
PolicyName: lambda
And the Lambda function resource has Role: !GetAtt lambdaIAMRole.Arn as part of its Properties field.
Based on the comments and inspection of the CloudFormation template, it was found that the issue was caused by the incorrect setup of IntegrationHttpMethod in apiGatewayLambdaResourceMethod.
Instead of
IntegrationHttpMethod: GET
it should be
IntegrationHttpMethod: POST
This is because AWS_PROXY for lambda requires POST method, not GET.

Unable to configure SQS queue notification in S3

I created an SQS queue and added policy under permission tab allowing only my account users to configure the configure the notification
Policy Document
{
"Version": "2012-10-17",
"Id": "arn:aws:sqs:us-east-1:111111111111:sqsqueue/SQSDefaultPolicy",
"Statement": [
{
"Sid": "Sid111111111111",
"Effect": "Allow",
"Principal": {
"AWS": "111111111111"
},
"Action": [
"sqs:SendMessage",
"sqs:ReceiveMessage"
],
"Resource": "arn:aws:sqs:us-east-1:111111111111:queue"
}
]
Navigate to S3 and try to configure event notification for the above queue, it is throwing an error
Unable to validate the following destination configurations. Permissions on the destination queue do not allow S3 to publish
notifications from this bucket.
(arn:aws:sqs:us-east-1:111111111111:queue)*
am I doing something wrong? Can someone help me please
I was able to resolve this issue by adding "Service": "s3.amazonaws.com"
in the Principal tag.
Here the policy document
{
"Version": "2012-10-17",
"Id": "arn:aws:sqs:us-east-1:111111111111:sqsqueue/SQSDefaultPolicy",
"Statement": [
{
"Sid": "Sid111111111111",
"Effect": "Allow",
"Principal": {
"Service": "s3.amazonaws.com"
},
"Action": [
"sqs:SendMessage",
"sqs:ReceiveMessage"
],
"Resource": "arn:aws:sqs:us-east-1:111111111111:queue"
}
]
This is explained in https://forums.aws.amazon.com/thread.jspa?threadID=173251
This template file creates a bucket, SQS Queue and a policy to connect the two:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
IncomingBucketName:
Type: 'String'
Description: 'Incoming Bucket Name'
Default: 'some-bucket-name-here'
Resources:
IncomingFileQueue:
Type: 'AWS::SQS::Queue'
Properties: {}
SQSQueuePolicy:
Type: 'AWS::SQS::QueuePolicy'
Properties:
PolicyDocument:
Id: 'MyQueuePolicy'
Version: '2012-10-17'
Statement:
- Sid: 'Statement-id'
Effect: 'Allow'
Principal:
AWS: "*"
Action: 'sqs:SendMessage'
Resource:
Fn::GetAtt: [ IncomingFileQueue, Arn ]
Queues:
- Ref: IncomingFileQueue
IncomingFileBucket:
Type: 'AWS::S3::Bucket'
DependsOn:
- SQSQueuePolicy
- IncomingFileQueue
Properties:
AccessControl: BucketOwnerFullControl
BucketName:
Ref: IncomingBucketName
NotificationConfiguration:
QueueConfigurations:
- Event:
s3:ObjectCreated:Put
Queue:
Fn::GetAtt: [ IncomingFileQueue, Arn ]
I was getting the same issue but used this page to work out how to connect the three resources in order to successfully deploy the stack:
https://aws.amazon.com/premiumsupport/knowledge-center/unable-validate-destination-s3/
I'm still working on the Policy Condition as the form recommended in the above link doesn't work for SQS. That being the case, the above template is not secure and shouldn't be used in production as it allows anyone to add messages to the queue.
I'll update this answer once I've figured that bit out...

Setting up cross account access of IAM user/role

I have a main account user that I want to allow access to a subaccount S3 bucket. I have setup the following stack in my subaccount
AWSTemplateFormatVersion : '2010-09-09'
Description: 'Skynet stack to allow admin account deploying user to access S3'
Parameters:
AccountId:
Type: String
Description: Account ID of admin account (containing user to allow)
Username:
Type: String
Description: Username to be allowed access
BucketPrefix:
Type: String
Description: Bucket to be allowed (prefix appended with -{AccountId}-{Region})
Resources:
CrossAccountRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
AWS:
- !Sub arn:aws:iam::${AccountId}:user/${Username}
Path: /
Policies:
- PolicyName: skynet-s3-delegate
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetObject
Resource: "*"
But I find that I still get an error when I try to assume the role:
aws s3 cp skynet-lambda.zip s3://skynet-lambda-TARGET_ACCOUNT_ID-ap-southeast-1 --profile skynetci-cross-account
An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::MAIN_ACCOUNT_ID:user/circleci-skynet is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::TARGET_ACCOUNT_ID:role/StackSet-df0e85b0-d6fd-47bf-a0bb-CrossAccountRole-1EW45TXEFAY0D
Why is this so considering I already have the following policy for the user
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": "arn:aws:iam::TARGET_ACCOUNT_ID:role/StackSet-df0e85b0-d6fd-47bf-a0bb-CrossAccountRole-1EW45TXEFAY0D"
}
You need to have Bucket Policy update to allow cross account access a sample policy would be like:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Example permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::AccountB-ID:root"
},
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::examplebucket"
]
}
]
}
Also make sure IAM user who is trying to access has this inline policy attached:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Example",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::examplebucket"
]
}
]
}
You can refer AWS Documentation
I am attaching a working example that I tested using two of my accounts.
STEP 1: CloudFormation YAML template:
AWSTemplateFormatVersion : '2010-09-09'
Description: 'Skynet stack to allow admin account deploying user to access S3'
Parameters:
AccountId:
Type: String
Description: Account ID of admin account (containing user to allow)
Username:
Type: String
Description: Username to be allowed access
BucketPrefix:
Type: String
Description: Bucket to be allowed (prefix appended with -{AccountId}-{Region})
Resources:
CrossAccountRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
AWS:
- !Sub arn:aws:iam::${AccountId}:user/${Username}
Path: /
Policies:
- PolicyName: skynet-s3-delegate
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetObject
Resource: "*"
RootInstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles:
-
Ref: "CrossAccountRole"
STEP 2: Create the cross account profile
Modify ~/.aws/credentials. Add a new profile called "skynetci-cross-account". Modify based upon your parameters created in STEP 1. You will need the role arn to replace the one below. You will also need the profile name for the account that you are giving permission to. In this example the profile name is "default".
Example here:
[skynetci-cross-account]
role_arn = arn:aws:iam::191070ABCDEF:role/Test-CrossAccountRole-IZDDLRUMABCD
source_profile = default
STEP 3: Test cross access
aws --profile skynetci-cross-account s3 ls s3://bucket-name
To accomplish your goal, you need to set a bucket policy in your target S3 bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DelegateS3Access",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::MAIN_ACCOUNT_ID:USER_NAME"
},
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::BUCKET_NAME/*",
"arn:aws:s3:::BUCKET_NAME"
]
}
]
}
And allow S3 permissions to this user.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Example",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"*"
]
}
]
}
In this case you do not need assume a role on the target account. The user itself will be able to access to bucket in another account.