In order to limit the number of images in a repository, I'd like to define a Lifecycle policy. Since all the stack is defined with CloudFormation, I'd like to define this policy too.
For example, my policy could be "keep only the most recent 8 images, no matter if tagged or not".
The solution was pretty easy, but since I could not find any example or similar questions (ECR is not mainstream, I know), let me post here the easy solution that I found, which simply requires to insert the policy as JSON into the CloudFormation definition:
MyRepository:
Type: AWS::ECR::Repository
Properties:
LifecyclePolicy:
LifecyclePolicyText: |
{
"rules": [
{
"rulePriority": 1,
"description": "Only keep 8 images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 8
},
"action": { "type": "expire" }
}]
}
Of course this is very simplistic, but it's the starting point that I was looking for
You can also define a reference to your PolicyText and later on your parameters.json stringify your policy.
It would look like something like this:
template.yml
Parameters:
lifecyclePolicyText:
Description: Lifecycle policy content (JSON), the policy content the pre-fixes for the microservices and the kind of policy (CountMoreThan).
Type: String
repositoryName:
Description: ECR Repository Name to which we will apply the lifecycle policies.
Type: String
registryId:
Description: AWS account identification number (12 digits)
Type: String
Default: xxxxx
Resources:
Repository:
Type: AWS::ECR::Repository
Properties:
LifecyclePolicy:
LifecyclePolicyText: !Ref lifecyclePolicyText
RegistryId: !Ref registryId
RepositoryName: !Ref repositoryName
Outputs:
Arn:
Value: !GetAtt Repository.Arn
parameters.json
[
{
"ParameterKey": "lifecyclePolicyText",
"ParameterValue": "{'rules':[{'rulePriority':1,'description':'Only keep 8 images','selection':{'tagStatus':'any','countType':'imageCountMoreThan','countNumber':8},'action':{'type':'expire'}}]}"
},
{
"ParameterKey": "repositoryName",
"ParameterValue": "xxxx"
}
]
| will allow you to add text inline.
AWSTemplateFormatVersion: "2010-09-09"
Resources:
ECRRepo:
Type: AWS::ECR::Repository
Properties:
RepositoryName: "images"
LifecyclePolicy:
LifecyclePolicyText: |
{
"rules": [
{
"rulePriority": 2,
"description": "Keep only one untagged image, expire all others",
"selection": {
"tagStatus": "untagged",
"countType": "imageCountMoreThan",
"countNumber": 1
},
"action": {
"type": "expire"
}
}
]
}
Related
I'm new to serverless, and trying to create a deployment using the Serverless 3 framework. I currently have some resources defined with references to other cfn resources that I've defined in my template, i.e.:
WebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
DependsOn:
- CloudFrontOriginAccessIdentity
- WebsiteBucket
Properties:
Bucket: WebsiteBucket
PolicyDocument:
Statement:
- Sid: PublicReadGetObject
Effect: Allow
Principal:
AWS:
Fn::Join: [ ' ', [ 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity', CloudFrontOriginAccessIdentity ] ]
Action:
- s3:GetObject
Resource:
- Fn::Join: [ '', [ 'arn:aws:s3:::', WebsiteBucket, '/*' ] ]
However, I keep getting cfn errors when I deploy as it seems that the references to these resources aren't resolving. I tried looking at the serverless-state.json output file to troubleshoot, and I see them defined as follows, for example:
"WebsiteBucketPolicy": {
"Type": "AWS::S3::BucketPolicy",
"DependsOn": [
"CloudFrontOriginAccessIdentity",
"WebsiteBucket"
],
"Properties": {
"Bucket": "WebsiteBucket",
"PolicyDocument": {
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": {
"AWS": {
"Fn::Join": [
" ",
[
"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity",
"CloudFrontOriginAccessIdentity"
]
]
}
},
"Action": [
"s3:GetObject"
],
"Resource": [
{
"Fn::Join": [
"",
[
"arn:aws:s3:::",
"WebsiteBucket",
"/*"
]
]
}
]
}
]
}
}
},
Am I referencing these incorrectly in my template?
To reference the resource correctly you need to use the Ref function:
Bucket: !Ref WebsiteBucket
Two more notes regarding your template:
You can omit the DependsOn section in this case as you are referencing all the resources you are waiting for. When using Ref, CloudFormation automatically creates the resources in the correct order (in this case it is Bucket and Access Identity first, Bucket Policy last)
Instead of using Join for combining strings and parameters, you can also use the Sub function which returns referenced values written like ${WebsiteBucket}, just as Ref/GetAtt does.
Template:
WebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref WebsiteBucket
PolicyDocument:
Statement:
- Sid: PublicReadGetObject
Effect: Allow
Principal:
AWS:
Fn::Join: [ ' ', [ 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity', !Ref CloudFrontOriginAccessIdentity ] ]
Action:
- s3:GetObject
Resource:
- !Sub arn:aws:s3:::${WebsiteBucket}/*
# OR:
# - Fn::Join: [ '', [ 'arn:aws:s3:::', !Ref WebsiteBucket, '/*' ] ]
Below is the my cloudformation template. I want to convert aws:SourceVpc into list in the resource policy document. I tried with spilt but cft below error.
Invalid policy document. Please check the policy syntax and ensure that Principals are valid. (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: xxxxx; Proxy: null)
Also devl and sdbx have 1 value where as others acnt and acpt have two values in mappings how can do it. Please suggest.
Output should like below.
aws:SourceVpc: ["vpc-7830jkd", "vpc-a1236"]
Mappings:
vpcid:
us-east-1:
sdbx: "vpc-1234"
devl: "vpc-2345"
acpt: "vpc-7830jkd,vpc-a1236"
us-east-2:
acnt: "vpc-a1236,vpc-7830jkd"
Parameters:
Env:
Type: String
Resources:
apigateway:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: mygateway
EndpointConfiguration:
Types:
- "PRIVATE"
Policy: !Sub
- |-
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": [
"execute-api:/*"
]
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": [
"execute-api:/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpc": !Split ["," , "${myappid}"]
}
}
}
]
}
- { myappid: !FindInMap [ vpcid, !Ref "AWS::Region", !Ref "Env" ] }
Output should like below. aws:SourceVpc: ["vpc-7830jkd", "vpc-a1236"]
Sadly, you can't do this. CFN is very limited in what it can do, and what you want is simply not achievable with plain CFN. You have to modify your Maps and provide all the vpc ids individually, not mix lists with individual values.
The other way would be through development of CFN macro or custom resource.
Is there an IAM policy that can be created/attached to CodeStarWorker-*-CloudFormation that limits either the Subnets or HostedZoneIds the CodeStar worker can use?
Here's an example template.yml:
Resources:
# other resources
DevAlb:
Properties:
LoadBalancerAttributes: []
Name: !Sub '${ProjectId}-dev-alb'
Scheme: internal
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets:
- !ImportValue PrivateSubnet1
- !ImportValue PrivateSubnet2
Tags:
- Key: Name
Value: !Sub '${ProjectId}-dev'
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
DevAlbDns:
Properties:
AliasTarget:
DNSName: !GetAtt
- AlbDev
- DNSName
HostedZoneId: !GetAtt
- AlbDev
- CanonicalHostedZoneID
HostedZoneId: !ImportValue InternalDomainDotCom
Name: !Sub '${ProjectId}.internal-domain.com'
Type: A
Type: 'AWS::Route53::RecordSet'
I don't want users with CodeStar access to import/use anything that would allow public internet access (without admin approval, anyway). How can I prevent someone from setting/importing PublicSubnet1 and PublicSubnet2 as one of the Subnets? Or prevent them from setting/import PublicDomainDotCom as the HostedZoneId?
I was able to do this by attaching the following policy to CodeStarWorker-app-CloudFormation!
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:GetChange",
"route53:GetHostedZone",
"route53:ListHostedZones",
"route53:ListHostedZonesByName",
"route53:ListResourceRecordSets",
"route53:GetHostedZoneCount",
"route53domains:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets",
"apigateway:GET"
],
"Resource": [
"arn:aws:route53:::hostedzone/REPLACE_WITH_HOSTED_ZONE_ID",
"arn:aws:apigateway:*::/domainnames"
]
}
]
}
This will only allow CodeStar's CloudFormation role to create a Route 53 record set in the hosted zone ID an admin has allowed.
I'm sure there are other ways to protect your infrastructure and data from bad actors with CodeStar roles. Feel free to share if you have any ideas (e.g., limiting EC2 VPCs/Subnets).
I tried the cloudformation template that I found here...
https://bl.ocks.org/magnetikonline/c314952045eee8e8375b82bc7ec68e88
It works as expected. But I will like to provide parameters to the post request. My Curl command should look something like this...
curl -d "mynumber=12345" -X POST https://tyin2sswj2.execute-api.us-east-1.amazonaws.com/mycall
How do I handle it at API gateway in the cloudformation template? I have already set the environment variable at lambda function level.
The template that does not work is this...
https://raw.githubusercontent.com/shantanuo/cloudformation/master/updated/lambda_api.tpl.txt
As it is clear that I am not able to pass the "mnumber" variable through the gateway.
I have updated my template and now it deploys function and gateway corretly. And still the URL generated does not work and shows "internal server error" message.
https://raw.githubusercontent.com/shantanuo/cloudformation/master/testapi.tpl.txt
You should change to using HTTP proxy integration.
Here is some info from AWS on proxy integration: https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-http-integrations.html
Try changing your RequestParameters from:
RequestParameters:
method.request.querystring.mnumber: false
to
RequestParameters:
method.request.path.proxy: true
and under integration from:
RequestParameters:
integration.request.querystring.mnumber: "method.request.querystring.mnumber"
to
RequestParameters:
integration.request.path.proxy: 'method.request.path.proxy'
This is a good tutorial on proxy integration with API Gateway:
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html
There are two ways you can access mynumber
Method 1 which works best with the SAM serverless API template. Define "API Gateway" and "Lambda". In Lambda definitions, call Events of type API:
This makes it where query strings are automatically picked up due to the event property. The parameters can be found in the event response that is passed into all lambda functions. It can be accessed with multiValueQueryStringParameters or queryStringParameters from the event object.
exports.getByDateHandler = async (event) => {
console.info(event.queryStringParameters);
console.info(event.multiValueQueryStringParameters);
}
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Description",
"Transform": ["AWS::Serverless-2016-10-31"],
"Resources": {
"getByDateFunction": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "src/handlers/getByDate/get-by-date.getByIdHandler",
"Runtime": "nodejs14.x",
"Architectures": ["x86_64"],
"MemorySize": 128,
"Timeout": 100,
"Events": {
"Api": {
"Type": "Api",
"Properties": {
"Path": "/date",
"Method": "GET"
}
}
}
}
}
},
"Outputs": {
"WebEndpoint": {
"Description": "API Gateway endpoint URL for Prod stage",
"Value": {
"Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
}
}
}
}
Method 2 which I havent tested is by defining the "Lambda", "API Gateway", "API Resource" and "API Methods". Linking the Lambda using the URI statement under "API Method".
for this method I only have a yaml example
MyLambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Description: "Node.js Express REST API"
FunctionName: "get_list_function" (The name in AWS console)
Handler: lambda.handler
Runtime: nodejs12
MemorySize: 128
Role: <ROLE ARN>
Timeout: 60
apiGateway:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "example-api-gw"
Description: "Example API"
ProxyResource:
Type: "AWS::ApiGateway::Resource"
Properties:
ParentId: !GetAtt apiGateway.RootResourceId
RestApiId: !Ref apiGateway
PathPart: '{proxy+}' OR "a simple string like "PetStore"
apiGatewayRootMethod:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: NONE
HttpMethod: ANY
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
IntegrationResponses:
- StatusCode: 200
Uri: !Sub >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations
ResourceId: !Ref ProxyResource
RestApiId: !Ref "apiGateway"
I am trying to create a S3 only user who will by definition have no access to any other resource. The user can upload and download files from S3. I have created a basic template that can be found here...
https://github.com/shantanuo/aws-cloudformation-templates/blob/master/aws/services/IAM/IAM_Users_Groups_and_Policies.yaml
But it is allowing access to cloudformation that is not necessary in my case. I have read the following pages, but do not know how to include them in my template.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
https://docs.aws.amazon.com/AWSCloudForation/latest/UserGuide/aws-properties-iam-group.html
What are the minimum parameters required to create a S3 only user?
Update:
Here is the cloudformation code that I tried and the error that I got:
error message: The following resource(s) failed to create: [CFNRole, CFNUser]. . Rollback requested by user.
The template:
Parameters:
NewUsername:
NoEcho: 'false'
Type: String
Description: New account username
MinLength: '1'
MaxLength: '41'
ConstraintDescription: the username must be between 1 and 41 characters
Password:
NoEcho: 'true'
Type: String
Description: New account password
MinLength: '1'
MaxLength: '41'
ConstraintDescription: the password must be between 1 and 41 characters
Resources:
CFNUser:
Type: AWS::IAM::User
Properties:
LoginProfile:
Password: !Ref 'Password'
UserName : !Ref 'NewUsername'
CFNRole:
Type: AWS::IAM::Role
Properties :
PermissionsBoundary : arn:aws:iam::aws:policy/AmazonS3FullAccess
RoleName : 'myPermissionBoundary'
CFNKeys:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref 'CFNUser'
Outputs:
AccessKey:
Value: !Ref 'CFNKeys'
Description: AWSAccessKeyId of new user
SecretKey:
Value: !GetAtt [CFNKeys, SecretAccessKey]
Description: AWSSecretAccessKey of new user
I have checked this link, but not sure how to include the policy in my current template.
https://aws.amazon.com/blogs/security/easier-way-to-control-access-to-aws-regions-using-iam-policies/
You are explicit giving your new user access to Cloudformation in your template.
You have this section:
CFNUserPolicies:
Type: AWS::IAM::Policy
Properties:
PolicyName: CFNUsers
PolicyDocument:
Statement:
- Effect: Allow
Action: ['cloudformation:Describe*', 'cloudformation:List*', 'cloudformation:Get*']
Resource: '*'
Groups: [!Ref 'CFNUserGroup']
CFNAdminPolicies:
Type: AWS::IAM::Policy
Properties:
PolicyName: CFNAdmins
PolicyDocument:
Statement:
- Effect: Allow
Action: cloudformation:*
Resource: '*'
Groups: [!Ref 'CFNAdminGroup']
In which your allow statements specifically provide access to Cloudformation. If you are trying to give access to s3 then why are you giving access to Cloudformation?
If you want a user to have access to s3 only, and going further one specific s3 bucket then your policy would look something like this (note this is in json):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:ListAllMyBuckets"
],
"Resource": "arn:aws:s3:::*"
},
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::YOUR-BUCKET",
"arn:aws:s3:::YOUR-BUCKET/*"
]
}
]
}
If you want your user to have access to all buckets, then your policy would look more like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1557048549844",
"Action": "s3:*",
"Effect": "Allow",
"Resource": "*"
}
]
}
See this source: https://objectivefs.com/howto/how-to-restrict-s3-bucket-policy-to-only-one-aws-s3-bucket