This guide explains how to set up Quilt across multiple AWS accounts, enabling you to separate your control plane (Quilt infrastructure) from your data plane (S3 buckets) for enhanced security, compliance, and organizational structure.
Common Use Cases:
In this guide, weβll configure two accounts:
βββββββββββββββββββββββββββββββββββββββ
β Control Account β
β ββββββββββββββββββββββββββββββββββββ
β β Quilt Infrastructure ββ
β β β’ CloudFormation Stack ββ
β β β’ Lambda Functions ββ
β β β’ Elasticsearch/OpenSearch ββ
β β β’ API Gateway ββ
β β β’ Web Application ββ
β ββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββ
β
β Cross-Account
β Access
βΌ
βββββββββββββββββββββββββββββββββββββββ
β Data Account β
β ββββββββββββββββββββββββββββββββββββ
β β S3 Buckets ββ
β β β’ Raw Data Bucket ββ
β β β’ Processed Data Bucket ββ
β β β’ Archive Bucket ββ
β ββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββ
Account Definitions:
Before starting, ensure you have:
Why This Matters: When Quilt (running in Control Account) writes objects to buckets in Data Account, you want the Data Account to own those objects for proper access control.
Implementation:
Using AWS CLI:
# Set object ownership to bucket owner enforced
aws s3api put-bucket-ownership-controls \
--bucket your-data-bucket \
--ownership-controls Rules='[{ObjectOwnership=BucketOwnerEnforced}]' \
--profile data-account
Why βBucket owner enforcedβ?
Purpose: Grant Quilt infrastructure in Control Account the necessary permissions to manage buckets in Data Account.
Create the Bucket Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "QuiltCrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::CONTROL-ACCOUNT-ID:root"
},
"Action": [
"s3:GetObject",
"s3:GetObjectAttributes",
"s3:GetObjectTagging",
"s3:GetObjectVersion",
"s3:GetObjectVersionAttributes",
"s3:GetObjectVersionTagging",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:PutObject",
"s3:PutObjectTagging",
"s3:GetBucketNotification",
"s3:PutBucketNotification"
],
"Resource": [
"arn:aws:s3:::your-data-bucket",
"arn:aws:s3:::your-data-bucket/*"
]
}
]
}
Apply the Policy:
Console Method:
CONTROL-ACCOUNT-ID and your-data-bucket)CLI Method:
# Save policy to file
cat > bucket-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "QuiltCrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"s3:GetObject",
"s3:GetObjectAttributes",
"s3:GetObjectTagging",
"s3:GetObjectVersion",
"s3:GetObjectVersionAttributes",
"s3:GetObjectVersionTagging",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:PutObject",
"s3:PutObjectTagging",
"s3:GetBucketNotification",
"s3:PutBucketNotification"
],
"Resource": [
"arn:aws:s3:::your-data-bucket",
"arn:aws:s3:::your-data-bucket/*"
]
}
]
}
EOF
# Apply the policy
aws s3api put-bucket-policy \
--bucket your-data-bucket \
--policy file://bucket-policy.json \
--profile data-account
π Security Note:
Quilt admins can still control user access to this bucket through the Quilt Admin Panelβs Roles and Policies. The bucket policy only grants access to Quilt infrastructure, not end users.
When You Need This: If youβre using EventBridge integration or have existing SNS topics in the Data Account that Quilt should use for notifications.
Create SNS Topic Policy:
Add this statement to your SNS topicβs resource policy in the Data Account:
{
"Sid": "QuiltCrossAccountSNSAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::CONTROL-ACCOUNT-ID:root"
},
"Action": [
"sns:GetTopicAttributes",
"sns:Subscribe",
"sns:Unsubscribe"
],
"Resource": "arn:aws:sns:region:DATA-ACCOUNT-ID:your-topic-name"
}
Apply SNS Policy:
# Get current policy
aws sns get-topic-attributes \
--topic-arn arn:aws:sns:region:DATA-ACCOUNT-ID:your-topic-name \
--attribute-names Policy \
--profile data-account
# Update policy (merge with existing statements)
aws sns set-topic-attributes \
--topic-arn arn:aws:sns:region:DATA-ACCOUNT-ID:your-topic-name \
--attribute-name Policy \
--attribute-value file://sns-policy.json \
--profile data-account
Configure in Quilt:
Why CloudTrail is Required:
Implementation Options:
If Quilt manages CloudTrail in the Control Account:
If you have existing CloudTrail in either account:
# List trails in Data Account
aws cloudtrail describe-trails --profile data-account
# List trails in Control Account
aws cloudtrail describe-trails --profile control-account
# Add data events for your bucket
aws cloudtrail put-event-selectors \
--trail-name your-trail-name \
--event-selectors '[
{
"ReadWriteType": "All",
"IncludeManagementEvents": true,
"DataResources": [
{
"Type": "AWS::S3::Object",
"Values": ["arn:aws:s3:::your-data-bucket/*"]
},
{
"Type": "AWS::S3::Bucket",
"Values": ["arn:aws:s3:::your-data-bucket"]
}
]
}
]' \
--profile data-account
If CloudTrail is in Data Account but Quilt needs access:
CloudTrail Bucket Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "QuiltCloudTrailAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::CONTROL-ACCOUNT-ID:root"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-cloudtrail-bucket",
"arn:aws:s3:::your-cloudtrail-bucket/*"
]
}
]
}
Final Configuration:
your-data-bucket# From Control Account, test bucket access
aws s3 ls s3://your-data-bucket --profile control-account
# Upload a test file
echo "Cross-account test" > test.txt
aws s3 cp test.txt s3://your-data-bucket/ --profile control-account
# Verify CloudTrail is capturing events
aws logs filter-log-events \
--log-group-name CloudTrail/YourLogGroup \
--filter-pattern "{ $.eventSource = s3.amazonaws.com }" \
--profile data-account
Symptoms:
Solutions:
Symptoms:
Solutions:
Symptoms:
Solutions:
Bucket Policy Refinements: Instead of granting access to the entire Control Account root, consider restricting to specific Quilt roles:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "QuiltSpecificRoleAccess",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::CONTROL-ACCOUNT-ID:role/QuiltLambdaRole",
"arn:aws:iam::CONTROL-ACCOUNT-ID:role/QuiltIndexerRole"
]
},
"Action": [
"s3:GetObject",
"s3:ListBucket",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::your-data-bucket",
"arn:aws:s3:::your-data-bucket/*"
]
}
]
}
VPC Considerations:
Example VPC Endpoint Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-data-bucket",
"arn:aws:s3:::your-data-bucket/*"
],
"Condition": {
"StringEquals": {
"aws:PrincipalAccount": ["CONTROL-ACCOUNT-ID"]
}
}
}
]
}
CloudWatch Alarms: Set up monitoring for cross-account access:
# Create alarm for failed S3 access attempts
aws cloudwatch put-metric-alarm \
--alarm-name "CrossAccountS3AccessFailures" \
--alarm-description "Monitor failed cross-account S3 access" \
--metric-name ErrorCount \
--namespace AWS/S3 \
--statistic Sum \
--period 300 \
--threshold 5 \
--comparison-operator GreaterThanThreshold \
--profile control-account
CloudTrail Monitoring: Monitor specific cross-account activities:
{
"eventVersion": "1.05",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AIDACKCEVSQ6C2EXAMPLE",
"arn": "arn:aws:sts::CONTROL-ACCOUNT-ID:assumed-role/QuiltRole/QuiltLambda",
"accountId": "CONTROL-ACCOUNT-ID"
},
"eventTime": "2024-08-26T10:30:00Z",
"eventSource": "s3.amazonaws.com",
"eventName": "GetObject",
"resources": [
{
"ARN": "arn:aws:s3:::your-data-bucket/file.csv",
"accountId": "DATA-ACCOUNT-ID"
}
]
}
Data Residency:
Audit Requirements:
For multi-region deployments:
# Replicate bucket policy across regions
for region in us-east-1 us-west-2 eu-west-1; do
aws s3api put-bucket-policy \
--bucket "your-data-bucket-${region}" \
--policy file://bucket-policy.json \
--region $region \
--profile data-account
done
CloudFormation Template for Bucket Policies:
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Cross-account bucket policies for Quilt'
Parameters:
ControlAccountId:
Type: String
Description: 'Control account ID where Quilt is deployed'
DataBucketName:
Type: String
Description: 'Name of the data bucket'
Resources:
CrossAccountBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref DataBucketName
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: QuiltCrossAccountAccess
Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${ControlAccountId}:root'
Action:
- 's3:GetObject'
- 's3:GetObjectAttributes'
- 's3:ListBucket'
- 's3:PutObject'
- 's3:DeleteObject'
Resource:
- !Sub 'arn:aws:s3:::${DataBucketName}'
- !Sub 'arn:aws:s3:::${DataBucketName}/*'
Need Help with Cross-Account Setup?
Success! You now have a secure, compliant cross-account Quilt deployment that separates your control plane from your data plane while maintaining full functionality.