This is a challenging problem as well as an important one to get right, particularly in your case where credentials are being provided to a third party.
It's not really possible/practical to fully automate this because the permissions required can be quite broad, especially when you consider that changes to your cloudformation template can easily result in more permissions being required, depending on what you happen to change. Often, updates to a stack may also trigger resource replacement, which essentially require all the permissions for deleting, creating, and updating. Utilizing a feature in one AWS service may require permissions of several other services, and so on.
Alternatively, is there some list of IAM permissions expected for each Cloudformation Resource
Because CloudFormation can do basically anything, this would quickly amount to wildcard permissions for the service/resource. Further, due to the countless interactions that are used between services, even within a single service offering, it's increasingly difficult to predict what permissions are needed. Things also change a lot depending on your environment. For example, you may or may not have already created a service-linked role required for managing services like RDS or ECS -- or you may have encryption enabled, requiring potential access to KMS or customer-managed keys or CloudHSM.
So, in short, the answer to your specific question is that it's not possible/practical to do this. As others have stated, you may be able to perform a set of tasks then observe which IAM actions were used, but this may be incomplete if you don't exhaust all the cases CFN might run into.
However, depending on your goals, there are several strategies you might use to strike a balance between ease of configuration and security:
Only allow actions via CloudFormation.
Using the aws:CalledViaFirst
global condition key, you can make sure that permissions granted by the policy are only in effect when they are taken via CloudFormation. In this case, the principal would not be able to take actions via the API directly, but only allows CloudFormation to perform those actions on behalf of the principal.
If you do this in combination with limiting which CloudFormation stack(s) the principal has permissions to Create/Delete/Update, that might make for a fairly well-defined permission boundary or at least a good place to start.
For example, part of your policy may be the following:
{
"Version":"2012-10-17",
"Statement":[
{
"Sid":"AllowDDBAndKMSActionsViaCFN",
"Effect":"Allow",
"Action":[
"dynamodb:GetItem",
"dynamodb:BatchGetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource":[
"arn:aws:dynamodb:region:111122223333:table",
"arn:aws:kms:region:111122223333:key/example"
],
"Condition":{
"StringEquals":{
"aws:CalledViaFirst":[
"cloudformation.amazonaws.com"
]
}
}
}
]
}
This way, the principal can't directly access DynamoDB or your KMS keys, for example, but a CloudFormation change launched requiring these permissions would still be permitted.
Even with this condition, because CloudFormation can manage just about anything, including IAM, you obviously would still want to be careful about which actions you grant, even via CFN.
Scope resources appropriately
This one is pretty basic. If you properly scope the Resource:
keys for your policies, this may help you reduce the blast radius of what this principal can manage. Additionally, you may consider condition keys for similar purpose. For example, you may have a naming convention that will allow you to specify resources as stack-name*
or use a condition key like a resource tag.
Define which actions you don't want permitted
Sometimes it's easier to define what you don't want an IAM role to be able to do, rather than defining what you want to allow. One obvious way is with "deny" policies, but a more powerful tool can be to use NotAction
to define this with more flexibility.
For example, you might want to allow every action except for certain 'dangerous' actions.
Statement": [
{
"Effect": "Allow",
"NotAction": [
"iam:*",
"organizations:*",
"account:*"
],
"Resource": "*"
}, ...
This will also give you the flexibility to use an additional statement to allow certain actions that we did not allow in the previous policy:
{
"Effect": "Allow",
"Action": [
"iam:CreateServiceLinkedRole",
"iam:DeleteServiceLinkedRole",
"iam:ListRoles",
"account:ListRegions"
],
"Resource": "*"
}
This would have not been possible if we had used Deny
for iam:*
in the previous policy. Using NotAction
with Allow
, we have more flexibility.
Restrict direct actions by source IP
Sometimes, your CICD will still need permissions for direct actions. Another layer of security can be added for direct actions by using the aws:SourceIp
condition key. If you self-host your CICD system or if your provider publishes its public IP addresses, you can use this condition to prevent your credentials being used from other systems.
Keep in mind that, when AWS services call other services on your behalf (like with CFN, but with many others), the SourceIp
key does not exist, so formulate your policies accordingly.
Example:
"Statement": [
{
"Sid": "PrincipalPutObjectIfIpAddress",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-service-bucket/*",
"Condition": {
"Bool": {"aws:ViaAWSService": "false"},
"IpAddress": {"aws:SourceIp": "123.45.167.89"}
}
}
Use short-lived credentials
In some CICD systems, it may be possible to inject temporary AWS credentials that are obtained via an AssumeRole call -- or you may automate this yourself using an API, for example. This way, if credentials are exfiltrated in a one-off circumstance, they won't be valid forever.
In summary, using techniques like these may help you quickly define policies that strike a good balance of being able to do most/all of what you need without being too permissive while mitigating risks that stem from the credentials being compromised or potential abuse/accidents.