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::CloudFormation::CustomResource - Metadata - Pass initiator of stack operation to custom resource provider #257

Open huribeir opened 4 years ago

huribeir commented 4 years ago

Scope of request

New feature to be used with AWS::CloudFormation::CustomResource resource type

Expected behavior

To be able to pass on the IAM entity (user/role) which triggered the stack operation over to the custom resource, alternatively including credentials.

Any additional context (optional)

The idea would be to allow a custom resource to be able to identify the IAM entity (user/role) which has triggered the stack operation and, consequently, the invocation of the custom resource. This can be useful in different ways, with two examples being:

An idea on how to implement this would be by creating a "metadata" section in the custom resource where you'd be able to specify certain properties that are included in the event object sent to the custom resource provider. Example:

Resources:
  MyCustomResource:
    Metadata:
      AWS::CloudFormation::CustomResourceRequestData: 
        include:
          - AWS::Initiator
          - InitiatorCredentials
          - [...]
    Type: Custom::MyCustomResource
    Properties:
      ServiceToken: !Ref MyLambdaFunction

In the above example, "AWS::Initiator" could be a pseudo-parameter that resolves to the user/role that triggered the change. The reason why this must be specified on a separate block like Metadata (instead of passing as a property to the custom resource) is to give the custom resource provider confidence that the data is accurate - i.e. that user 'Bob' isn't trying to appears as if the request came from user 'John'.

In addition to that, a complimentary feature would be to pass down the credentials of the "initiator" down to the custom resource (perhaps similarly to how CloudFormation impersonates the user to run macros). This would allow the Custom Resource provider to perform actions using the same credentials as the invoking user, like it happens with native requests.

rjlohan commented 4 years ago

In addition to that, a complimentary feature would be to pass down the credentials of the "initiator" down to the custom resource (perhaps similarly to how CloudFormation impersonates the user to run macros). This would allow the Custom Resource provider to perform actions using the same credentials as the invoking user, like it happens with native requests.

To clarify, CloudFormation only impersonates the user to call lambda:InvokeFunction, we don't pass any additional credentials down to the macro function.

rjlohan commented 4 years ago

Now for the requirements you're asking for;

validating that the user/role performing the stack operation has rights to use the custom resource

If that user has lambda:InvokeFunction permissions on their IAM Policy, this is inferred as permission to use the custom resource and controlling that action is how you can control access to the resource.

allowing the custom resource to use that information to perform customized actions

Can you give an example of what you're aiming to achieve here?

brcourt commented 4 years ago

@rjlohan, thanks

There are Many use-cases where this use-case is immensely valuable. In my organization, I created a centrally managed custom resource platform. In a Shared-Services account live the custom resource provider functions, which are shared by hundreds of accounts in the organization. A custom resource routing functions exists in each organization account, that routes custom resource requests to any specific custom resource in the Shared-Services account, and attaches attributes to the requests (SNS message attributes), which allows the custom resource provider functions to validate that requests actually originated from the account/region listed in the request. This prevents a forged request from triggering a Lambda function and launching resources in another account.

The issue however is that this is the most granular I can get in the scenario above. I can limit access an a per account basis, however I have many accounts with multiple tiers of users. I can limit permissions based on the resource type of the custom resource, however this doesn't solve all use cases. Below are some use-cases that REQUIRE the identity of the requestor to be known (and validated!)

  1. Custom resource to create GitHub webhook - custom resource provider must authorize the requester has access to the specified GitHub repo.
  2. Custom resource to create Jenkins job - our organization has Jenkins configured with groups/roles/directories etc, and allowing custom resource to configure these jobs relative to the requester's permissions is required.
  3. Custom resource that manages cross-account resource creation - we have pipelines that span 3 or more accounts. This custom resource would trigger a job to build resources in multiple accounts to setup this pipeline, however requester must be authorized to create resources in each account.

As you can see, without knowing the identity of the requester, ANYONE who has invoke permissions can forge a request to the Lambda function and trigger the custom resource. If I forge the request, change my name to Bob, I can create webhooks to Bob's repos, escalating my permissions. I can create Jenkins Jobs using Bob's resources. This is a complete blocker for these custom resources.

To address your statements:

If that user has lambda:InvokeFunction permissions on their IAM Policy, this is inferred as permission to use the custom resource and controlling that action is how you can control access to the resource.

This is true. The feature request isn't asking for CloudFormation to authorize the requester. The feature request is to validate the requester's identity. This simply means passing caller information to the Lambda function in the context, or including Message Attributes in an SNS message. My custom resource will authorize the requester. The point is, without knowing the identity of the requester, no authorization is possible, even custom logic provided via the custom resource. As you can see from the examples above using lambda:Invoke permissions is not even close to being scalable enough to confidently prevent unauthorized access across hundreds of accounts.

...this is inferred as permission to use the custom resource

Further wanted to point out that this is the exact point of the feature request. I do not want lambda:Invoke permissions to be the sole entry point gating access to a custom resource. I would like to write my own logic to authorize use of my organization's custom resources.

Can you give an example of what you're aiming to achieve here?

Literally any third party association would benefit from this feature request. Any organization that manages accounts with Landing Zone or Control Tower, or any organization with multiple accounts, or decentralized access across accounts. Integrating with any third party service with RBAC simply requires mapping those roles to federated claims, which a custom resource can provide -- but only if I can validate the identity of the requester.

I have an AWS case opened with @huribeir going into more detail regarding my immediate use-cases, and some extreme workarounds I used to actually implement this feature on my own.

brcourt commented 4 years ago

I do want to point out that the Metadata example in the initial post won't quite work to validate the integrity of the request data. I can forge the entire request set to Lambda. What I ended up requesting during my investigation into various workarouds are the following implementations

  1. Always pass the initiator identity to Lambda in the context of the invocation (no workaround for SNS)
  2. Create a new CFN API call named ValidateCustomResourceRequest.
    • Passing the request ID recieved in the custom resource request should return the exact same request data and a completion status
    • Should a SUCESS or FAILED response be sent to the pre-signed url generated in the CFN request, this API call should return mark the status as COMPLETED
benkehoe commented 4 years ago

The new CloudFormation resource framework should be evaluated as to whether it sufficiently meets the requirements in this issue. https://aws.amazon.com/blogs/aws/cloudformation-update-cli-third-party-resource-support-registry/

rjlohan commented 4 years ago

Thanks for the reminder to come back to this @benkehoe. Obviously I couldn't fully disclose that we were about to release the CloudFormation CLI & Registry when this issue was opened but now that it's here I think we are able to address some of the asks here.

@brcourt - using the new platform you are now able to create named resource types, e.g MyOrg::GitHub::WebHook and using IAM Policy Conditions, you can control access within your account to specific resource types (something you could not achieve with Custom Resources). See the cloudformation:ResourceTypes condition here: https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awscloudformation.html#awscloudformation-policy-keys

For the multi-account scenario, StackSets is our defacto way to achieve cross-region, cross-account deployments. All resources published to the CloudFormation Registry are available to StackSets so I think this addresses another gap that Custom Resources had - though you would still need to RegisterType with each account, you can use a consistent template across accounts (since you no longer need to specify a Lambda ARN to invoke, which would be different in each account). As we evolve the Registry, we will make these 'sharing' use cases even simpler.

brcourt commented 4 years ago

I don't see how this addresses the feature request. I explained what I have setup in my environment, and yes that can be replaced with this new feature. The ask however is to pass identity information to a custom Resource so that the custom Resource can authorize access on a per-user basis, not a per-account basis. I need my custom Resource providers to only authorize actions for resources that the updater has access to. In my example, the updater would only be allowed to create a webhook for repos that the they (who is federated) also is an Admin for in an Enterprise Github team.

Can you show me how I can use these new features to pass identity information of the person who initiated the stack update?

lwoggardner commented 4 years ago

Would this requirement be solved if lambda itself included AWS provided (and thus non spoofable) caller context when invoked directly? e.g. like it does with cognito information. There are various feature requests for that around.

brcourt commented 4 years ago

@lwoggardner, This would absolutely resolve my requirements.

benm5678 commented 1 year ago

Can we get it to actually run the custom resource lambda as the role of the person who deployed cloudformation - that is, get it to assume role of the caller. In our case, custom resource calls an API in another account that is protected by IAM, and we'd like to capture the user identity there (even if accessed outside the construct).