How to generate the AWS root account ARN in CloudFormation? - amazon-web-services

The situation
I am generating a KMS Key in CloudFormation. According to the KMS policy documentation, it is crucial to create a policy where the Principal is the account itself, in order for IAM policies to be able to grant access to the key.
The question
How can I create the ARN for the account root in CloudFormation?

For those who use YAML for their CloudFormation templates:
!Sub arn:aws:iam::${AWS::AccountId}:root

The answer
{
"Fn::Join":[
":",
[
"arn:aws:iam:",
{
"Ref":"AWS::AccountId"
},
"root"
]
]
}
Why does this work?
First, let's examine the line, "Ref":"AWS::AccountId". This is a pseudo parameter reference, which is a fancy way of saying that it is a parameter that comes out of the box with CloudFormation. There are many such parameters. This one happens to give us the account ID, which is crucial for constructing the ARN.
Now, the rest is just the creation of an ARN using this account ID. Fn::Join is simply a CloudFormation built-in that allows concatenation of strings. This is crucial when combining references with string constants (or other references) as we are doing here.
The result is something like...
arn:aws:iam::123456789012:root

Related

Use already existing KMS key in Lambda functions that are managed through SAM/CloudFormation

The situation is:
A lambda function is created using a SAM/CloudFormation. A policy is attached to allow this function to access an existing bucket (in the same region). This looks somehow like this:
MyFunction:
Type: AWS::Serverless::Function
Properties:
.....
Policies:
- Statement:
- Effect: Allow
Action:
- "s3:ListBucket"
- "s3:PutObject"
- "s3:GetObject"
Resource:
- "arn:aws:s3:::my-great-existing-bucket"
- "arn:aws:s3:::my-great-existing-bucket/*"
This means: Without modifying the policy of the existing bucket, the newly created lambda-function will now have access to that bucket.
But in this case this already existing bucket is additionally encrypted with an existing customer managed KMS key (again in the same region) so access will still be denied to the Lambda function. The goal would be to add the policy to use that existing key also directly to cloud-formation template.
I found this link:
https://docs.aws.amazon.com/kms/latest/developerguide/iam-policies.html
My interpretation would be, that the named goal would usually be achieved only the other way round:
You need to modify the policy of the KMS-key. But they Key is of course not managed via the Cloud Formation Stack and therefore not manageable here (or am I wrong about that?).
So the only way to achieve it would be to activate general IAM Policies for the KMS-key?
https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default-allow-root-enable-iam
Is that understanding correct?
So the only way to achieve it would be to activate general IAM Policies for the KMS-key?
Yes. That's correct. If your customer managed KMS key does not have it, you have to modify KMS policy to allow lambda role. If you activate general IAM Policies, then you can just modify the lambda execution role with permissions to the KMS key.

How to get a role's name/ARN into its own AssumeRole policy?

I've got a CloudFormation stack that creates several IAM roles. I have the somewhat unusual-sounding requirement to include a statement in the role's AssumeRole policy preventing the role from assuming itself. The role names are constructed using Fn::Join with the value of a parameter, so in order to reproduce the role name and construct its ARN in the policy statement, I have to duplicate that expression.
Is there any way to cache the result of the expression so that I don't have to repeat it? There are several of these roles and each one has the same issue. Can I define a parameter whose default value includes the value of another parameter and reference that? Or can I use Fn:GetAtt inside the definition of a resource to access other attributes of the resource being defined (such as its name)?
If not, is there another solution besides repeating the name-construction expressions? This is something I would put in the locals block in Terraform...
Since this wound up not being possible, I ran this shell one-liner to update each role after creation, to prevent it from assuming itself:
aws iam update-assume-role-policy \
--role-name "$role" \
--policy-document file://<(
aws iam get-role --role-name "$role" |
jq '.Role | .Arn as $arn | .AssumeRolePolicyDocument |
.Statement+=[{"Effect": "Deny",
"Action": "sts:AssumeRole",
"Principal": { "AWS": $arn }}]')
Is there any way to cache the result of the expression so that I don't have to repeat it?
Sadly, there is no such way in plain CloudFormation.
Can I define a parameter whose default value includes the value of another parameter and reference that?
Unfortunately, parameters can't refer other parameters.
Or can I use Fn:GetAtt inside the definition of a resource to access other attributes of the resource being defined (such as its name)?
You can't get any return values of a resource before it is fully constructed.
Having sad that, if you know the role name up-front, you could construct its ARN yourself. Role ARNs have known format. However, you want be able to use it as a Principal in the same role, as principles must be valid. Thus, you can't use ARN of a role as a principle before it actually exists.
If you want to add ARN of a role to be its own Principal, you would have to construct a custom resource in CloudFormation. Thus, you first create your role normally, and then use the custom resource (in a form of lambda function) to update it's Principal element.
Also since you have to duplicate a lot of elements in your templates, a general solution to this is by means of CloudFormation macros.
Either way, it seems to me that because your requirements regarding templates are not normally supported out of the box, you will have to develop some custom resources and/or macros to fully satisfy them. With custom resources and macros you can basically achieve what ever you want, but it will require extra development.

Cloudformation AWS IAM Role with and without RoleName Parameter

When creating a new IAM role resource using Cloudformation, AWS::IAM::Role.
There is an optional RoleName parameter, and I am not sure when if it is better practice to include the RoleName parameter or exclude it.
The following are the differences that are specified in the documentation
If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the role name.
If you specify a name, you must specify the CAPABILITY_NAMED_IAM value to acknowledge your template's capabilities.
Naming an IAM resource can cause an unrecoverable error if you reuse the same template in multiple Regions.
Are there any other functional differences betweeen using or excluding the RoleName parameter?
This will simply generate a name for you if you don't specify it (constructed from the stack and resource name with a random string at the end).
The only difference is re-usability. If you want to recreate the stack multiple times, it would be better to pass in this IAM role ARN as a parameter.
I'd recommend not naming, which will allow you the freedom to create the stack as many times as you want without the fear of breaking additional resources.
If you really must the recommendation from AWS is below:
Naming an IAM resource can cause an unrecoverable error if you reuse the same template in multiple Regions. To prevent this, we recommend using Fn::Join and AWS::Region to create a Region-specific name, as in the following example: {"Fn::Join": ["", [{"Ref": "AWS::Region"}, {"Ref": "MyResourceName"}]]}.
The documentation explains that it may be dangerous to name your roles:
Naming an IAM resource can cause an unrecoverable error if you reuse the same template in multiple Regions. To prevent this, we recommend using Fn::Join and AWS::Region to create a Region-specific name, as in the following example: {"Fn::Join": ["", [{"Ref": "AWS::Region"}, {"Ref": "MyResourceName"}]]}.

AWS IAM Roles and policies in simple English?

I've been working with the AWS PHP SDK and I seem to get everything except the IAM Roles and permissions.
Can someone please explain to me in the simplest term how the IAM roles work and explain the following terms: StatementId, Action, ARN and most importantly Principal in simple English?
To give you the source of my confusion, here is a problem I recently faced. I'm trying to create an API Gateway in which a Resource's method triggers a Lambda function. It wasn't working until I copy pasted this bit:
$lambdaClient->addPermission([
'FunctionName' => 'fn name',
'StatementId' => 'ManagerInvokeAccess',
'Action' => 'lambda:InvokeFunction',
'Principal' => 'apigateway.amazonaws.com',
]);
But in some other thread someone suggested to use the following for the same:
const permissions = {
FunctionName: target,
StatementId: 'api-gateway-execute',
Action: 'lambda:InvokeFunction',
Principal: 'apigateway.amazonaws.com',
SourceArn: 'arn:aws:execute-api:' + nconf.get('awsRegion') + ':' + nconf.get('awsAccountId') + ':' + nconf.get('apiGatewayId') + '/*'};
How come the the first one doesn't contain any account info but The second one does? Also then there is another person who has pasted something totally different to get the same working for him. There are so many keys in the last example (like "Fn::Join"), I don't even know where to begin and what it does.
How does one figure out where to find these policies? Do we just copy-paste them from somewhere is there is a way to ascertain them. If so what keys must always be specified.
Any help will be appreciated because I'm totally confused right now.
First of all, Welcome to the world of AWS !!! :-D
Let me try to explain your doubts about how to understand IAM(in general) with an analogy.
Think that there is an organization called ORG1.
Deparments of ORG1: HR-dept, Test-dept, DEV-dept
Employees of ORG1: EMP1, EMP2, EMP3 ... EMP10
Members of HR dept: HR1, HR2, HR3
Now I want to create a role for HR dept to give them permission to hire/suspend an employee. The policy will look like below:
{
"Version": "2012-10-17", // This is version of the template. Don't change this. This is NOT a date field for your use.
"Statement": [
{
"Sid": "SOME-RANDOM-ID-WITH-NUMBER-1P1PP43EZUVRM", // This is used as ID in some cases to identify different statments
"Principal": HR-dept, // the dept who is allowed to assume this role or the one who is allowed to invoke this role
"Effect": "Allow", // has only 2 values: ALLOW/DENY. Either You want to provided the below privileges or you want to striped off these privileges.
"Action": [
"hire",
"suspend",
], // these are privileges which are granted
"Resource": "EMP1", // the entity on whom do you want to apply those actions on. In this case employee EMP1.
"Condition": {
"ArnLike": {
"AWS:SourceArn": "HR*" // You want anyone from HR-dept whose id starts with HR to be able to execute the action.ie HR1,HR2 or HR3 .
}
}
}
]
}
Now try to understand the below code from the same perspective(Internally this code creates a template similar to above):
const permissions = {
FunctionName: target,
StatementId: 'api-gateway-execute', // This is just an ID. Dont sweat about it.
Principal: 'apigateway.amazonaws.com', //which entity group the invoker belongs to
Action: 'lambda:InvokeFunction', // The privilege you are giving to API gateway api's
SourceArn: 'arn:aws:execute-api:.. blah blah blah' // ie. the exact Id of api-gateway which all has rights to invoke lambda function
};
In AWS ARN is a unique ID of a resource. Kind of like EmployeeId in a company.This is unique globally.
Believe me, At first it may seem that what you are trying to do in AWS is difficult to comprehend, But at some point you will start getting comfortable as you go on crossing each hurdle you face. And then you will admire at how customizable AWS features are.
How does one figure out where to find these policies?
You need to refer the AWS Documentation for specific service to find out what are the principals, actions and statements they support. For example if you need to find out policies for DynamoDB, check DynamoDB API Permissions. It can be confusing at first, since AWS Need to cater using IAM to authorize all of their services, but it becomes straight forward over time.
Let me explain each part of the policy
StatementId(Sid) - Its just and optional statement identifier (e.g 1, 2, abcd & etc.) and for some services(e.g SQS, SNS) it requires uniqueness.
Action - What your policy allows to do on a AWS Service. e.g For DynamoDB you can allow creating Tables, Putting new items & etc. For EC2 instance, it can allow starting and stopping.
ARN(Amazon Resource Name) - This is a unique name to uniquely identify AWS resources like a EC2 server, S3 bucket, DynamoDB table and even IAM policy, Role & etc.
Principal - Principal is to restrict who is allowed to use this policy. It can be a user (IAM user, federated user, or assumed-role user), AWS account, AWS service, or other principal entity that is allowed or denied access to a resource.
In addition you need to include Resource parameter, where you can either use a wildcard '*' or a ARN with Account ID within it.
I think most of the answers are correct but here it is from the horse's mouth/the great AWS document (full credit)
Role: An IAM role is an IAM identity that you can create in your account that has specific permissions.
Policies: IAM policies define permissions for an action regardless of the method that you use to perform the operation
Typically you have a role and you assign polices to your role.
To answer last part of your question "How does one figure out where to find these policies". This is all depends on what you are trying to do but always start with the least amount of permission (same concept as linux file permission don't give 777 ). How do you define your policies there are standard one already defined in your AWS account but you can use a tool to customize yours policies using the below tool
https://awspolicygen.s3.amazonaws.com/policygen.html

AWS Managed Policy Vs Policy

Can someone explain to me the difference between an AWS Policy and an AWS Managed Policy in the context of Cloud Formation?
More specifically, I'm trying to define an auto scaling template where:
Each instance in an auto scale configuration is assigned an IAM Instance Role that has a policy.
The same policy is applied to the user when they try and access these instances.
I'm trying to keep duplication to a minimum and it seems like I may be able to achieve it via a Policy linked to a role, and group of users. The role can then be associated with EC2 Instance via instance profile and users can be added to the groups which in turn are assigned the policy.
Why and under what circumstances would one use a ManagedPolicy?
Thank you for your assistance.
EDIT: It seems like Role requires a policy document irrespective. So even having a separate policy won't really help? Or am I missing something?
AWS::IAM::Role only requires a trust policy. The Policy/Managed Policy can be defined separately.
The difference between AWS::IAM::ManagedPolicy and AWS::IAM::Policy is that AWS::IAM::ManagedPolicy does not require you to assign a Group, Role or User when defining it. AWS::IAM::Policy does. In your use case, you're probably fine using AWS::IAM::Policy.
If I may add, testing Policy creation using CDK v2.12.0, groups, users or roles are not required. iam.ManagedPolicy creates a policy you can share, iam.Policy is created as an inline policy.
new iam.Policy(this, 'testPolicy2', {
statements: policyDocs,
//groups: [s3UserGroup],
policyName: 'testPolicy2'
})
new iam.ManagedPolicy(this, 'testPolicy3', {
statements: policyDocs,
//groups: [s3UserGroup],
managedPolicyName: 'testPolicy3'
})