Skip to content

CloudFormation: Write CUR to S3 bucket in different account

0

Updated CFN template based on @Leo K's answer:

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  S3ClientBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-fancy-bucket-name
      BucketEncryption: 
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: 'AES256'
      PublicAccessBlockConfiguration:
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  S3ClientBucketAccessPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: my-fancy-bucket-name
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
              - billingreports.amazonaws.com
          Action:
            - s3:GetBucketAcl
            - s3:GetBucketPolicy
          Resource:
            - !GetAtt S3ClientBucket.Arn
          Condition:
            StringLike:
             aws:SourceAccount: '<remote account id here>'
        - Effect: Allow
          Principal:
            Service:
              - billingreports.amazonaws.com
          Action:
            - s3:PutObject
          Resource:
            - !Sub ${S3ClientBucket.Arn}/*
          Condition:
            StringLike:
              aws:SourceAccount: '<remote account id here>'

I have created a bucket in one AWS account like so (previous attempt, does not work):

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  RemoteAWSAccountId:
    Type: String

Resources:
  S3ClientBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-fancy-bucket-name
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: alias/aws/s3
      PublicAccessBlockConfiguration:
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  S3ClientBucketAccessPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: my-fancy-bucket-name
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            AWS: !Sub arn:aws:iam::${RemoteAWSAccountId}:root
          Resource:
            - !GetAtt S3ClientBucket.Arn
            - !Sub "${S3ClientBucket.Arn}/*"
          Action:
            - s3:PutObject
            - s3:GetBucketAcl

As far as I understand, this should grant <RemoteAWSAccountId> access to write to that bucket, but trying to create a CUR in the "remote account" using the following template fails with:

Event name: PutReportDefinition
Error code: ValidationException

Here is the template:

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  CostAndUsageReport:
    Type: AWS::CUR::ReportDefinition
    Properties:
      AdditionalSchemaElements: 
        - RESOURCES
      Compression: Parquet
      Format: Parquet
      RefreshClosedReports: true
      ReportName: my-fancy-report
      ReportVersioning: CREATE_NEW_REPORT
      S3Bucket: my-fancy-bucket-name
      S3Prefix: cur
      S3Region: us-east-1
      TimeUnit: DAILY

Everything is in us-east-1.

There is this post here already, but it didn't help me.

3 Answers
2
Accepted Answer

I tested it and with the exact same configuration that works in the local account, it fails when the bucket is in a different account, so apparently the point in documentation about the bucket having to be "in your account" means that it has to exist in the same account where the report originates.

You could still aggregate account-level reports from multiple accounts by generating the exports in each account locally and using S3 replication to replicate the data to a central bucket. S3 replication supports both cross-account and cross-region scenarios.

EXPERT
answered a year ago
EXPERT
reviewed a year ago
EXPERT
reviewed a year ago
  • Thanks again heaps @Leo K!!

1

The reports aren't written by your AWS account where you set up the CUR export. They're written by the AWS service principal "billingreports.amazonaws.com", which populates the aws:SourceAccount, aws:SourceArn, aws:SourceOrgID, and other related condition keys in the request context to allow you to ensure that the reports are written to your bucket only on behalf of accounts you want to be able to do that. The principal doesn't exist in your account, so the typical cross-account permissions model doesn't apply.

You'll need policy statements in your S3 bucket policy described in this document article: https://docs.aws.amazon.com/cur/latest/userguide/cur-s3.html. You'll need to replace the ${AccountId} placeholder with the account ID where the CUR export is configured.

EDIT: Looking at the document more closely, it seems to be a bit wrong. This: "aws:SourceArn": "arn:aws:cur:us-east-1:${AccountId}:definition/*" naturally won't work with the StringEquals operator but requires StringLike or ArnLike for the * to be interpreted as a wildcard. You can either change the operator or just drop the aws:SourceArn check entirely; aws:SourceAccount will suffice nicely, if you control the origin account or otherwise trust it.

I haven't tested if the CUR export actually permits the bucket to reside in a different account. The documentation says somewhat loosely that the bucket must be "in your AWS account", which might mean that it has to reside in the same account with the export, or might be meant as a general statement about just needing a bucket you control.

EXPERT
answered a year ago
  • I guess the answer to the last paragraph would be most important ^^ Thanks again @Leo K for the quick answer and the insights! I have updated my post with the updated CFN template to create the bucket and policy.

1

Regarding the latest update, you can't use SSE-KMS encryption for the bucket's default encryption with an AWS-managed KMS key, when the requests are made by a principal outside your AWS account. AWS-managed keys are only available for local use within the account.

The service principal isn't in your account, so you have two options. You can create a customer-managed KMS key and allow the kms:Decrypt and kms:GenerateDataKey actions to the service principal in the key policy of the KMS key, with the same aws:SourceAccount restriction as you're using for the bucket. The other option is to use basic SSE-S3 encryption for your bucket, which doesn't involve any key management or authorisation by the customer.

EXPERT
answered a year ago
  • Updated my post again and executed it as well. CFN template to create the bucket passes now and the S3 bucket with attached BucketPolicy and SSE-S3 exists, but I still cannot create the CUR in the "remote" account. Same meaningless error as before :( Event name: PutReportDefinition and Error code: ValidationException.

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.