Restrict acces to API Gateway endpoint to VPC in cloudformation - amazon-web-services

I'm trying to limit access to my API Gateway endpoints to requests from my VPC. There are examples of API Gateway Resource Policies, and even a Policy property on the RestApi resource, but I can't figure out how to write a policy that needs the API's ID, when the API hasn't been created yet.
I have an example of my understanding how a stack should look like, based on the AWS documentation:
MyRestApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: My Great API
Policy:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action: execute-api:Invoke
Resource:
Fn::Join:
- - ''
- 'arn:aws:execute-api:'
- Ref: region
- ':'
- Ref: accountId
- ':'
- Ref: MyRestApi
- Effect: Deny
Principal: '*'
Action: execute-api:Invoke
Resource:
Fn::Join:
- - ''
- 'arn:aws:execute-api:'
- Ref: Region
- ':'
- Ref: AccountId
- ':'
- Ref: MyRestApi
Condition:
StringNotEquals:
"aws:SourceVpc":
Ref: VpcId
The crux is that I can't reference MyRestApi in the policy when it's still being created. I'm sure I'm not the only one that wants to do this ... I'd rather think this is a common problem, so there is very likely an answer already I haven't found yet.
Thanks for any help,
Stefan
PS: The documentation I used was https://docs.aws.amazon.com/de_de/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-policy and https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-resource-policies-examples.html

According to AWS documentation the policy supports a special syntax for Resource due to this problem.
"Resource": [
"execute-api:/stage/method/path"
]
In the comments, they call it:
// simplified format supported here because apiId is not known yet and partition/region/account can derived at import time

Related

AWS Cloudformation:Template validation error Role and policy

I am new to cloudformation and trying to create a template that can create a execution role and associated policies for my lambda function.
AWSTemplateFormatVersion: 2010-09-09
Description: AWS CloudFormation Template for creating iam role for SSM lambda
Parameters:
rolename:
Type: String
Description: The name of the iam role for SSM Lambda
Default: SSM_lambda_role
policyname:
Type: String
Description: pcluster lambda iam policy for SSM Lambda
Default: SSM_lambda_policy
Resources:
ssmlambdarole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Sub '${rolename}'
Description: iam role for ssm lambda role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
ManagedPolicyArns:
- !Sub 'arn:aws:iam::${AWS::AccountId}:policy/${policyname}'
ssmlambdapolicy:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
ManagedPolicyName: !Sub '${policyname}'
Description: The name of the iam role for SSM Lambda
Path: '/'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- logs:CreateLogGroup
Resource: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
Effect: Allow
Sid: CloudWatchLogsPolicy
- Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${policyname}:*
Effect: Allow
Sid: CloudWatchLogsPolicy
- Action:
- ssm:Describe*
- ssm:Get*
- ssm:List*
Resource: "*"
Effect: Allow
If I define a role first in the above template, I get an error during stack creation mentioning that the policy is not found and if I create policy first in the above order, I keep getting a validation error. can someone tell me where am I getting wrong.
There is an attribute that can help to achieve that: DependsOn,
but the better way is to use - !Ref ssmlambdapolicy instead of - !Sub 'arn:aws:iam::${AWS::AccountId}:policy/${policyname}'.
In each case, it will establish a dependency between resources. Thanks to that AWS will be able to recognize resource creation orders - you didn't use any of them, so AWS 1stly tries to create a role (or policy, depending on the order in the template), and attach a policy that doesn't exist yet.
The validation error is due to that you missed !sub in the policy statements.
Btw, I strongly recommend looking for help in CFN documentation - sometimes there is a section with use-case examples.

Reference arn of a Elasticache cluster in cloudformation

I would like to reference the arn of a "going-to-be-created" Redis ElastiCache cluster in a cloud formation template.
This is the ElasticacheCluster template (tested and working in cloudFormation)
ElasticacheCluster:
Type: AWS::ElastiCache::CacheCluster
Properties:
AutoMinorVersionUpgrade: 'true'
Engine: redis
CacheNodeType: cache.t2.micro
NumCacheNodes: '1'
VpcSecurityGroupIds:
- Fn::GetAtt:
- ElastiCacheSecurityGroup
- GroupId
CacheSubnetGroupName:
Ref: ElastiCacheSubnetGroup
I cut on the other staff like subnetgroup and security group because it is also not relevant here. I should grant access to the Cluster to another resource with an Access Policies here what I was trying:
AccessPolicies:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: "*"
Action: es:*
Resource: !GetAtt ElasticacheCluster.Arn
- Effect: Allow
Principal:
AWS: "*"
Action: es:*
Resource: !GetAtt ElasticacheCluster.Arn
Condition:
IpAddress:
aws:SourceIp: 0.0.0.0/0
I saw this:
the !GetAtt ElasticacheCluster.Arn for the resource entry
here but seems not to be working in this case since !GetAtt is returning a fixed set of attributes and ARN is not one of them (as suggested by #Anton in the comments.
I also saw this other question that could solve the issue but I would prefer a not-harcoded-solution being not dependent on things like region and account id.
The solution to the problem seems to be simple but I am struggling to find a clean answer.
I took inspiration from both the #multiscup answer and the one referenced in the question.
This approach is far from clean and I am waiting for a better answer but at least it is working.
The main idea is to construct the string needed for the arn:
arn:aws:elasticache:region:account-id:cluster:resource-name
To do that I used a join trying to dynamically get the element thanks to the built-in CloudFormation functions:
Resource: !Join
- ':'
- - 'arn:aws:elasticache'
- !Ref 'AWS::Region'
- '<your-account-id>'
- 'cluster'
- !FindInMap [Elasticache, Redis, cluster-name]
I used a Map to define the Redis-cluster because I was using the same value also in other points in the CloudFormation template. Maybe you might find helpful to have the map as well
Mappings:
Elasticache:
Redis:
cluster-name: redis-demo
As Anton mentioned, GetAtt only has certain attributes that you can get. Also, the !Ref will return the logical ID of the resource. Have you thought about trying to use the User and/or the UserGroup resources to accomplish what you want?

AWS AppSync Lambda Authorizer

I just implemented a lambda resolver in AWS AppSync. The lambda and AppSync live in different projects; The template that provisions the function writes the function ARN to SSM and the template that builds AppSync pulls that SSM parameter down and assigns that ARN to an AdditionalAuthenticationProvider.
The deploy process goes in order synchronously; Lambda (create auth function, set ARN to SSM param) -> AppSync (create API, retrieve SSM param and assign to authorization provider).
When I examine the console, I can see the correct function ARN is assigned as the authentication provider to AppSync.
The problem: when I go to issue a request, the lambda is never invoked, I can check CloudWatch and verify no invocations - I am just met with the response.
{
"errors" : [ {
"errorType" : "BadRequestException"
} ]
}
If I do not provide a value to the authorization header, I get a 401 - which is the expected behavior of the lambda authorization directive, rejecting any requests without a value in that header before proceeding to the function.
So it would appear that something isn't plumbed correctly, something is missing that I can't find in a doc to allow invocation.
The gotcha: if I go into the console and assign this same function ARN manually, everything works fine and stays working fine. It would seem that, perhaps, the console is doing something behind the scenes that my deploy is not, but I cannot seem to correctly identify what is missing.
I've been following this document https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-lambda-authorization and one note gives me pause - and I have set these trust permissions, AFAIK.
Lambda functions used for authorization require a principal policy for appsync.amazonaws.com to be applied on them to allow AWS AppSync to call them. This action is done automatically in the AWS AppSync console
Here is the SAM template (without input params)
Resources:
ServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ lambda.amazonaws.com, appsync.amazonaws.com ]
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: logs
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- Effect: Allow
Action:
- xray:*
Resource: "*"
- PolicyName: ssm
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: ssm:*
Resource: "*"
LambdaPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref AuthorizationFunction
Action: lambda:Invoke
Principal: appsync.amazonaws.com
AuthorizationFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: <code-uri>
Handler: app.lambda
Runtime: nodejs14.x
Role: !GetAtt ServiceRole.Arn
Tracing: Active
FunctionARNParameter:
Type: AWS::SSM::Parameter
Properties:
Type: String
Name: <name>
Value: !GetAtt AuthorizationFunction.Arn
Maybe typing it out my problem was just what I needed. The last thing I tried, LambdaPermission was the key - but the action was incorrect and needed to be InvokeFunction.
I also chose to assign the FunctionName as the lambda ARN instead of the name
LambdaPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt PPSAuthorizationFunction.Arn
Action: lambda: InvokeFunction # <--
Principal: appsync.amazonaws.com
Hope this is useful to someone!

how to get Rolename from existing Lambda function on cloud via its friendly name or arn using cloudformation?

I have created a CFN template for rotating secret of RDS. It consist of a AWS HostedLambda which calls for an autogenerated Role. I want to attach another policy to that role.
rRotationLambdaDecryptPolicy:
Type: AWS::IAM::ManagedPolicy
DependsOn: rSecretRotationScheduleHostedRotationLambda
Properties:
Description: "Providing access to HostedLambda for decrypting KMS"
ManagedPolicyName: CustomedHostedLambdaKmsUserRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowLambdaDecryptKMS
Effect: Allow
Action:
- kms:Decrypt
- kms:CreateGrant
Resource:
- !Sub arn:aws:kms:*:${AWS::AccountId}:key/*
Condition:
ForAnyValue:StringLike:
kms:ResourceAliases: alias/SecretsManager_KMSKey
Roles: <friendly rolename>
Problem is i know the Lambda friendly name and its Arn. Need to find the rolename linked to this lambda so that i can attach the above policy to it(Add its friendly name to Roles).
tried attaching this below
Roles:
Fn::Join:
- ""
- - '"'
- Fn::GetAtt:
- !Sub 'arn:aws:lambda::${AWS::AccountId}:function:SecretsManager-research-creds-rotation-lambda'
- Role
- '"'
P.S.- cant use importvalue here, because the nested stack has been created by AWS and its output doesn't consist of export.
Lambda used:-
rSecretRotationSchedule:
Type: AWS::SecretsManager::RotationSchedule
Properties:
SecretId:<SecretId>
HostedRotationLambda:
KmsKeyArn: <KmsKeyArn>
MasterSecretArn: <MasterSecretArn>
MasterSecretKmsKeyArn: <MasterSecretKmsKeyArn>
RotationType: PostgreSQLMultiUser
RotationLambdaName: SecretsManager-research-creds-rotation-lambda
VpcSecurityGroupIds: <VpcSecurityGroupIds>
VpcSubnetIds: <VpcSubnetIds>
RotationRules:
AutomaticallyAfterDays: 60
Below is the link from which i took reference for template:-
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-rotationschedule.html
Most likely you can't do this with plain CFN. You would have to develop a custom resource. This would be in the form of a new lambda function which would use AWS SDK to query attributes of your primary lambda function. Once the custom resource finds the role name it would return it to your stack for further use.

AWS API Gateway not invoking specified lambda

I recently deployed a new API endpoint and linked it to a specified lambda via cloud formation.
x-amazon-apigateway-integration:
uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/arn:aws:lambda:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':function:'
- !Ref lbdBusinessPersonAccountsFunctionName
- '-'
- !Ref EnvName
- ':live'
- /invocations
When I check the API Gateway console, It seems like the lambda is correctly integrated to the API endpoint. But when I check the lambda console itself, no API GW trigger event was generated.
When I try manually link it to the gateway, it doesn't let me specify the resource I want to link it to, but instead creates a new resource under the lambda name.
Is there a manual linking step that I am missing?
On your cloudformation template, assuming you are using YAML; add the permissions mapping resource below. This gives apigateway the permission to add trigger/invoke your lambda (this is in addition to the appropriate IAM permissions).
FunctionInvokePermissions:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt "Function.Arn"
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub
- "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApiId}/${pathpart}/${method}"