Skip to content

Consume cross-account secret from a CloudFormation Registry Resource Type

0

I am working on a set of CloudFormation Registry custom resource types that require access to an API token. These resources will run in a collection of accounts part of an AWS Organization. The API token is managed in a centralized account and is regularly rotated through a lambda function. I managed to limit permission to the KMS key to the secret and secretsmanager service, and I am able to limit the resource policy on the secret to the AWS Organization. However, as soon as I attempt to limit access to the service principal 'resources.cloudformation.amazonaws.com', they lose access. This is through the 'Principal:Service' key and in the few available condition keys with 'Principal' set to '*', including 'ViaAWSService' and the 'Principal' variant.

When I remove the service principal, and review the CloudTrail logs of a successful GetSecretValue call, I see "invokedBy": "resources.cloudformation.amazonaws.com" in both the requesting account, and the account containing the secret.

I like to prevent that 'normal' user roles might access these credentials. Only our team has permissions to deploy custom resource types.

Working policies:

Execution role of resource type:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "kms:Decrypt",
                "secretsmanager:GetSecretValue"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

Secret Resource Policy:

{
  "Version" : "2012-10-17",
  "Statement" : [ {
    LOCKOUT_PREVENTION / ROTATION_PERMISSIONS
  }, {
    "Effect" : "Allow",
    "Principal" : "*",
    "Action" : "secretsmanager:GetSecretValue",
    "Resource" : "*",
    "Condition" : {
      "StringEquals" : {
        "aws:PrincipalOrgID" : "ORGID"
      }
    }
  } ]
}

KMS Key Resource Policy:

{
    "Version": "2012-10-17",
    "Id": "key-default",
    "Statement": [
        {
            LOCKOUT_PREVENTION
        },
        {
            ROTATION_PERMISSIONS
        },
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "kms:Decrypt",
                "kms:DescribeKey"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "kms:ViaService": "secretsmanager.REGION.amazonaws.com",
                    "aws:PrincipalOrgID": "ORG_ID"
                },
                "ArnLike": {
                    "kms:EncryptionContext:SecretARN": "arn:aws:secretsmanager:REGION:SECRET_OWNER:secret:SECRET_NAME"
                }
            }
        }
    ]
}

As a reminder, after the wall of policy text, when introducing 'resources.cloudformation.amazonaws.com' as a restriction, I am unable to access the secret.

Can someone help me ?

5 Answers
1

Hi,

Cross-account secret access is fully detailled in this page: https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_examples_cross.html

To allow users in one account to access secrets in another account (cross-account access), 
you must allow access both in a resource policy and in an identity policy. This is different 
than granting access to identities in the same account as the secret.

You must also allow the identity to use the KMS key that the secret is encrypted with. 
This is because you can't use the AWS managed key (aws/secretsmanager) for cross-account
 access. Instead, you must encrypt your secret with a KMS key that you create, and then attach 
a key policy to it. There is a charge for creating KMS keys. To change the encryption key for a secret, 
see Modify an AWS Secrets Manager secret.

Go to this page to have the full samples of the IAM policies that you need to define in both accounts.

Best,

Didier

EXPERT

answered 2 years ago

EXPERT

reviewed 2 years ago

  • Thank you for your prompt reply, I am aware of that page. However, it does not describe interactions with 'Principal:Service:' which I am aiming for here with resources.cloudformation.amazonaws.com. Or a combination of a list of allowed roles and a condition limiting the role to the said Amazon AWS endpoint if I want it fully least privileged covered. No user has to have access to this secret, only the custom resource types.

0
Accepted Answer

When I wrote the previous message, I was already falling in a cached-response trap. Short answer: No. SourceArn is not communicated trans accounts.

Leaving me with the harsh reality that I will need to keep a list of all the unambiguous full Role ARNs as the only way to separate user roles from CFN Type roles.

In short: The answer to my original question is No. Currently, you cannot filter for resources.cloudformation.amazonaws.com.

answered 2 years ago

0

To address your issue with cross-account secret access from CloudFormation Registry Resource Types, while ensuring that only the CloudFormation service can access the secret, you need to make some adjustments to your policies. Here's how you can modify them:

  1. For the Execution role of the resource type, keep it as is. This role needs the necessary permissions to access the secret and decrypt it.

  2. For the Secret Resource Policy, modify it to explicitly allow the CloudFormation service:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFormationCustomTypesToRetrieveThisValue",
      "Effect": "Allow",
      "Principal": {
        "Service": "resources.cloudformation.amazonaws.com"
      },
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:PrincipalOrgID": "ORGID"
        }
      }
    }
  ]
}
  1. For the KMS Key Resource Policy, add a specific statement for CloudFormation:
{
  "Version": "2012-10-17",
  "Id": "key-default",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "resources.cloudformation.amazonaws.com"
      },
      "Action": [
        "kms:Decrypt",
        "kms:DescribeKey"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:PrincipalOrgID": "ORG_ID"
        },
        "ArnLike": {
          "kms:EncryptionContext:SecretARN": "arn:aws:secretsmanager:REGION:SECRET_OWNER:secret:SECRET_NAME"
        }
      }
    }
  ]
}

These changes should allow the CloudFormation service to access the secret while preventing access by normal user roles. The policies now explicitly allow the CloudFormation service principal while still maintaining the organization ID restriction.

To further enhance security, you can use the aws:SourceArn and aws:SourceAccount condition keys in your policies. These keys can help prevent the confused deputy problem by ensuring that the CloudFormation service is acting on behalf of the expected resources and accounts.

Remember to replace placeholders like ORGID, REGION, SECRET_OWNER, and SECRET_NAME with your actual values. Also, ensure that you keep any necessary statements for lockout prevention and rotation permissions in your policies.

If you're still experiencing issues after implementing these changes, you may need to review the trust relationships of the IAM roles involved and ensure that the CloudFormation service has the necessary permissions to assume these roles across accounts.

Sources
Cross-service confused deputy prevention - AWS CloudFormation
Get a secret or secret value from Secrets Manager - AWS CloudFormation

answered 2 years ago

EXPERT

reviewed 2 years ago

  • The autogenerated response does not work. As soon as I introduce "Principal": {"Service": "resources.cloudformation.amazonaws.com"},, the type loses access. The error is: because no resource-based policy allows the secretsmanager:GetSecretValue action

0

Apologies in advance for the response that is seeped in frustration.

After a weekend of research and trail and error, I can conclude it is currently impossible to use resources.cloudformation.amazonaws.com as an endpoint filter in resource-based policies. Even though the CloudTrail log shows that it is invokeby, has the useragent of, and ipsource from resources.cloudformation.amazonaws.com. No condition-key or principal will evaluate these properties. CalledVia only supports a limited list which contains cloudformation, but not resources.cloudformation. PrincipalServiceName or any other condition-key don't evaluate the invokedBy field. SourceIp isn't evaluated by the sourceIPAddress condition-key as expected by the logs values.

Why can't I use the role-arn as filter? Let me answer, before I get another generic response assuming I haven't done any research before posting a question. It will not be 1 role that will access this secret. It is a collection of custom types, with a collection of versions as each is an individual deployment for custom resource types within the CloudFormation Registry, over many accounts, in many regions. This list will have massive administration overhead, and I assume I will either have to use ArnLike to begin with to fit it within the byte limit of the resource policy. Which opens it still up for the bad user role attack I want to prevent. We are the only team that can deploy these custom CloudFormation types. This secret will only be consumed by this endpoint.

Just for reference, I put down the full cloudtrail log to show the absurdity of this. I do recognize that the orgId is absent from the log, which does work within the policy, and the log might be incomplete. Still, I see no way forward other than to do a step backwards in cross account secret best practices and create a role inside the secret's owner account that my resources can assume. And experiment if the trust policy of an IAM role is able to do what I want it to do.

{
    "eventVersion": "1.11",
    "userIdentity": {
        "type": "AWSAccount",
        "principalId": "ARO_PID",
        "accountId": "ACCOUNT",
        "invokedBy": "resources.cloudformation.amazonaws.com"
    },
    "eventTime": "TIMESTAMP",
    "eventSource": "secretsmanager.amazonaws.com",
    "eventName": "GetSecretValue",
    "awsRegion": "REGION",
    "sourceIPAddress": "resources.cloudformation.amazonaws.com",
    "userAgent": "resources.cloudformation.amazonaws.com",
    "requestParameters": {
        "secretId": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:adminapplicationtoken"
    },
    "responseElements": null,
    "requestID": "GUID",
    "eventID": "GUID,
    "readOnly": true,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "ID",
    "sharedEventID": "4cb3c29c-d4b5-49fc-aebe-be47a117d968",
    "eventCategory": "Management"
}

I will be back and report if that works. Or that I have invested time into an abandoned product and just should have used the old-fashioned imho less secure lambda backed custom resources. All I can say is that the inactivity of the cli-tool should have warned me.

answered 2 years ago

0

After more research, trail and error, I have come to the following conclusion: CloudTrail is sanitized and does not show all information about what happens with your CloudFormation Resource Type session. The program runs within theresources.cloudformation.amazonaws.com account (aka 096709234663), yet it actually uses credentials that are tied to your account and assumed within your account.

CloudFormation Custom Resource Types have a built-in mechanism that ties the resource to an account through a type activation. This creates the link that is the base role that you get from using the base boto3.client(). From the Registration Service, you receive a session_proxy with the assumed role you configured in your type version registration.

What is then logged to CloudTrail is sanitized to hide sensitive internal data.

What I wrote above is 95% speculation on what happens within the magic AWS sauce that is CloudFormation Registry. But it is the mental model that makes most sense to me at this time.

So, whatever I want to do with the endpoint, it is not possible:

  • PrincipalServiceName and Principal:Service: wrong from the beginning.
  • SourceIp is sanitized, but not the actual value of the event that is evaluated.
  • CalledVia does not support resources.cloudformation

I think I can still limit things to CloudFormation resources by using aws:SourceArn. I will report back when I got a working implementation.

answered 2 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.