aws-cloudformation / cloudformation-coverage-roadmap

The AWS CloudFormation Public Coverage Roadmap
https://aws.amazon.com/cloudformation/
Creative Commons Attribution Share Alike 4.0 International
1.11k stars 54 forks source link

AWS::KMS::KeyPolicy is desired #322

Open rix0rrr opened 4 years ago

rix0rrr commented 4 years ago

AWS::KMS::Key supports configuring a resource policy as a property on the object, but not as its own resource. Given that by default, keys must have a statement both in the key resource policy as well as on the IAM identity policy to allow an operation such as iam:Encrypt, this makes it impossible to create a Key with restrictive permissions in Stack 1, and a Role in Stack 2 that can use that key.

This is because at the time of Key creation (Stack 1), when we're setting the key policy, we won't know the name of the Role yet that will be created in Stack 2, so we can't properly reference it.

What we would like to be able to write is this:

==== STACK 1 ============
Resources:
    MyKey:
        Type: AWS::KMS::Key
        Properties: 
            Policy: ... # <-- can't refer to MyRole here!
Outputs:
    KeyArn: 
        Value: { Fn::GetAtt: [MyKey, Arn] }
        Export: SharedKeyArn

==== STACK 2 ============
Resources:
    MyRole: 
        Type: AWS::IAM::Role
        Properties: # ...
    RolePolicy:
        Type: AWS::IAM::Policy
        Properties:
            PolicyDocument:
                Version: 2012-10-17
                Statement:
                - Effect: Allow
                    Action: 'kms:Decrypt'
                    Resource: { Fn::ImportValue: SharedKeyArn }
            Roles:
                - {Ref: MyRole}
    ChangeKeyPolicy:  # <-- MUST encode this operation in Stack 2
        Type: AWS::KMS::KeyPolicy
        Properties:
            KeyArn: { Fn::ImportValue: SharedKeyArn }
            PolicyDocument:
                Version: 2012-10-17
                Statement:
                - Effect: Allow
                  Action: 'kms:Decrypt'
                  Principal: { Fn::GetAtt: [MyRole, Arn] }

Compare: AWS::S3::BucketPolicy, AWS::SQS::QueuePolicy, etc, which encapsulate the operation of adding to a resource's policy, so that this operation can be done in a cross-stack fashion.

5. Helpful links

A grant must be present in both the key policy and the identity's IAM policy. Source:

https://docs.aws.amazon.com/kms/latest/developerguide/control-access-overview.html#managing-access

IAM policies by themselves are not sufficient to allow access to a CMK.

6. Category (required) - Will help with tagging and be easier to find by other users to +1

  1. Security (IAM, KMS...)
rix0rrr commented 4 years ago

The suggested workaround for this at the moment is to set up the key policy to allow blanket permissions to anyone in the account, but that seems less than desirable for something as sensitive as encryption keys:

{
  "Sid": "Enable IAM User Permissions",
  "Effect": "Allow",
  "Principal": {"AWS": "arn:aws:iam::111122223333:root"},
  "Action": "kms:*",
  "Resource": "*"
}

https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html

KMS team thoughtfully built in the restriction that users need to be very explicit about access permissions to sensitive objects (changing behavior from every other AWS service on purpose), only for their efforts to be undone because the restrictions are so cumbersome to work with in practice that people don't bother and just enable all access again.

acdebaca commented 4 years ago

Hi! We studied this issue in depth at my organization and converged on the following approach. We always create role stacks first, KMS CMK stacks second, and all other resources subsequently based on dependency order.

This design not only permits us to import role arns into key policies, but we can also layer in role policies that specify the KMS keys as well because multiple role policies can be created separate resources:

1. Role  <---+
    ^        |----  3. Role Policy
2. Key   <---+

We further build on this model by layering in additional inline role policies on resources created later. For example, consider how we might provide read access to a CF-created role to an S3 bucket using SSE-KMS encryption with a KMS CMK with both role and resource policies:

1. Role <-+------------------+--------------------------------+
    ^     |- 3. Role Policy  |- 4. SSE-KMS S3 bucket+policy <-+- 5. Role Policy
2. Key  <-+------------------+
  1. Role
  2. Key + Policy referencing role (e.g. kms:Decrypt, kms:GenerateDataKey*)
  3. Role Policy referencing role and key (e.g. kms:Decrypt, kms:GenerateDataKey*)
  4. S3 Bucket referencing key + policy referencing role (NB: bucket policies are separate resources, but you can only have one bucket policy per bucket) (e.g. s3:GetObject)
  5. Role Policy referencing role and bucket (e.g. s3:GetObject)

Note that resource policies reference roles as AWS principals, whereas policies reference roles via the Roles property in an AWS::IAM::Policy stack template.

The general pattern:

rix0rrr commented 3 years ago

Found a use case where a {"AWS": "arn:aws:iam::111122223333:root"} grant wouldn't even suffice: encrypted queues. See linked issue.

kz974 commented 3 years ago

Yes please. Would help a lot in avoiding circular dependencies between resources when deploying stacks!

zoomzoomcloud commented 3 years ago

Pretty please. It makes no sense that AWS::KMS::KeyPolicy resource type doesn't exist.

jpr5 commented 3 years ago

I too have the circular dependency problem.

I read all the comments and, more importantly, tried to grok @acdebaca 's response in the context of what I was trying to do. Must have scratched a hole in my chin, trying so hard to understand - and to no avail.

My view of the problem is simple: if I can do it in the console, I should be able to do it via CloudFormation. I figured out what I needed to do in the console, then wrote the equivalent in CloudFormation YAML - which resulted in a circular dependency. Then I went to Former2, which accesses your AWS account and produces somewhat-usable YAML to describe the architectural bits you select. Funny, that - it too produced something with a circular dependency.

Simplification of what I was doing:

Bucket:
    - encrypt with Key

Key:
    - inline: grant (ec2 instance) Role permission to use key

Role:
    - inline: ec2 instance can assume Role
    - reference Policy

Policy: 
    - grant permission to access Bucket

There's the circle -- Bucket -> Key -> Role -> Policy -> Bucket. It's what I constructed manually in the console, and what Former2 produced as well. However, simplistically, to break the circle, one of the dependencies just needs to point in the other direction. Then there's no more circle.

I took what the OP @rix0rrr was saying as essentially asking for a way to point the Key's policy in the other direction - to create a policy separately and associate it post-facto key creation (and post-facto the Role it was referencing). I agree with them, why this doesn't exist -- and really, why the AWS team thought their time was better spent exploring and producing some frankly convoluted way to achieve an outcome that basically means the user needs to take on a lot more work and totally change their mental model, rather than just add the mechanism -- is all beyond me. I still haven't figured out how to apply what they suggested, and I'm honestly not that stupid.

That all said, I found another spot to point a dependency in the other direction. I removed the Policy reference from Role, and used the Roles: property of Policy to point back to the Role. Now I get:

Bucket:
    - encrypt with Key

Key:
    - inline: grant (ec2 instance) Role permission to use key

Role:
    - inline: ec2 instance can assume Role

Policy: 
    - grant permission to access Bucket
    - roles: Role

And voila, the circle broken.

Later I decided to move "grant Role permission to use Key" from Key to Policy as "grant permission to use Key" (no Principal), which in retrospect makes more logical sense. FWIW.

Hopefully this helps someone. Cheers.

JohnPreston commented 2 years ago

New use-case

andreaschiappacasse commented 2 years ago

My use case:

I want to produce a script to migrate the content of a kms encrypted bucket from one source account to a target account.

I would like to have a cdk stack that is only deployed when a migration occurs (and destroyed after it finishes) and that is capable of adding the required permissions for the target account to run CopyObject via Boto3 on the source account bucket.

The stack should manage S3 permissions on existing buckets (via BucketPolicy) and KMS permissions on existing keys, but the latter seems impossible right now, and would be easily solved having a KeyPolicy which can be used by the stack dedicated to the temproary migration resources.

shearn89 commented 1 year ago

Yep, (possibly not, but throwing in my 2p) another use case:

Having this resource allows me to break up the ordering the same way I do with IAM::Role and IAM::Policy.

chchang6 commented 1 year ago

I'll add another current use case, but I guess it's basically the generalization of this issue.

I don't want anyone (role) touching the key, only specific principals, AND I don't want the role touching any key, only the key relevant to it.

It comes down to necessity AND sufficiency. Possessing the role should be necessary to access the key, and sufficient to do so. If I drop the key policy, no one can touch it; if I generalize, then everyone can touch it. If I generalize the role policy, then it can touch too many keys. If we're going to have this two-sided approach (policy on key, and policy on role, both must match up), then we should not have circular dependency be an issue.

Ditto to jpr5's comment--if something can be done in console, there should be a way to do it in CloudFormation. Adding to that, common things should be easy. As security becomes a bigger concern and more people are actually paying attention to IAM and key policies, this particular problem should get rectified.

twixr commented 4 months ago

I seem to have the same issue. My use case:

But that already results in a circular dependency.

benbridts commented 4 months ago

@twixr For that specific case you don't have to give the SNS Topic permissions to use the key, but the producers / consumers of that topic, so you would:

twixr commented 4 months ago

@benbridts thanks, but is it possible to attach a role to a SNS Topic, I thought it wasn't possible. So then I would have a role with a policy to access KMS. But how do I tell SNS that it has to have that role?

Edit: Ah I misread, so the permission to encrypt should be granted to the resource that publishes to the topic? Although, when I publish via the AWS Console, my user / role would have access to the KMS key but the SNS message isn't published correctly. So not sure how that works then.

benbridts commented 4 months ago

Edit: Ah I misread, so the permission to encrypt should be granted to the resource that publishes to the topic? Although, when I publish via the AWS Console, my user / role would have access to the KMS key but the SNS message isn't published correctly. So not sure how that works then

Indeed.

If this fails, you might be missing the delegation to IAM on the KMS key itself (in contrast with s3 bucket policies, for KMS you need permissions in both the Resource policy and Principal policy)