Migrate S3 Data Between AWS Accounts

Move data across AWS accounts using cross-account IAM roles and the CLI.

Introduction

30 Second Summary

When companies grow, merge, or reorganize, their data often needs to move between separate AWS accounts. What seems like a simple copy turns into an "Access Denied" wall because AWS isolates accounts by default.

In this project, you will migrate data stored in S3 from one AWS account to another by building a cross-account IAM role and assuming it from the CLI.

What You'll Build

You can run a single CLI command from your terminal and watch files flow from one AWS account's S3 bucket into another account's bucket, powered by a cross-account IAM role you built from scratch.

By the end of this project, you'll have:

  • A working cross-account S3 migration where you run one command and files appear in a completely separate AWS account's bucket.
  • Hands-on experience diagnosing Access Denied errors by building proper trust policies and permissions policies that make two accounts trust each other.
  • A reusable CLI profile that automatically assumes the cross-account role, turning future migrations into a one-liner.
  • Secret Mission: Set up S3 Cross-Region Replication for automatic ongoing sync between accounts.

Are there any prerequisites?

You need one existing AWS account to start. You will create a second free-tier AWS account during the first step using a different email address.

Familiarity with the AWS Console is helpful but not required. Every action is spelled out step by step.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Tools and Accounts

Cross-account migration requires two separate AWS accounts and a working command-line tool to orchestrate the data transfer between them.

In this step, you will install the AWS CLI, set up a second AWS account, and configure named profiles so your terminal knows which account to talk to.

In this step, get ready to:
  • Install and verify AWS CLI v2 on your Windows machine.
  • Create a second AWS free-tier account with an IAM user for programmatic access.
  • Configure two named CLI profiles (source and destination) and verify both.
Check and install AWS CLI v2

The AWS CLI is the command-line tool you will use to create buckets, copy files, and assume roles across your accounts.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.
  • Check if AWS CLI is already installed by running this command:
aws --version

Look at the output in your terminal and pick the tab that matches what you see.

✔️ I see aws-cli/2.x.x

Your AWS CLI v2 is already installed and ready to go. You can move on to the next substep.

ⓧ I see aws-cli/1.x.x

You have AWS CLI version 1 installed. This project requires version 2 because it handles role assumption and named profiles more reliably.

  • Install AWS CLI v2 by running this command in PowerShell:
irm https://awscli.amazonaws.com/v2/install.ps1 | iex
  • Close your PowerShell window completely and reopen it.
  • Verify the installation by running:
aws --version

You should now see aws-cli/2.x.x in the output. The new version takes precedence over the old one.

ⓧ Command not found

AWS CLI is not installed on your machine yet. Let's install it now.

  • Install AWS CLI v2 by running this command in PowerShell:
irm https://awscli.amazonaws.com/v2/install.ps1 | iex
  • Close your PowerShell window completely and reopen it.
  • Verify the installation by running:
aws --version

You should now see aws-cli/2.x.x in the output.

Still seeing command not found?

Make sure you fully closed and reopened your PowerShell window after installing. The PATH update only takes effect in a new session.

If you still get an error, check that the install script completed without errors. Try running it again.

help me troubleshoot why aws --version is not working after installing AWS CLI v2 on Windows

Create a second AWS account and IAM users

You already have one AWS account. To simulate a real cross-account migration, you need a second account to act as the destination.

  • Open https://aws.amazon.com/ in your browser.
  • Click Create an AWS Account in the top right corner.
  • Use a different email address than your first account.
  • Complete the sign-up process to create a free-tier account.

Why do I need two accounts?

AWS accounts are completely isolated from each other by default. A user in Account A has zero visibility into Account B's resources. This isolation is exactly what makes cross-account migration interesting. You will build the bridge between them.

Now create an IAM user with programmatic access in each account. You will do this twice: once in your source account and once in your new destination account.

  • Sign in to the AWS Management Console for your source account.
  • Open the IAM console at https://console.aws.amazon.com/iam/.
  • In the left navigation pane, select Users, then click Create user.
  • For User name, enter migration-source-user.
  • Click Next.
  • On the Set permissions page, choose Attach policies directly.
  • Search for and select AmazonS3FullAccess and IAMFullAccess.
  • Click Next, then Create user.

Next, create an access key for this user.

  • Click on the user name you just created to open the user details page.
  • Select the Security credentials tab.
  • Under Access keys, click Create access key.
  • For the use case, choose Command Line Interface (CLI) and click Next.
  • Click Create access key.
  • Click Download .csv file to save your access key ID and secret access key. Store this file somewhere safe.
  • Click Done.

Save your keys securely

This is your only opportunity to view or download the secret access key. If you lose it, you will need to create a new access key. Save the CSV file in a secure location.

Now repeat the same process for your destination account.

  • Sign out of your source account and sign in to your destination account.
  • Open the IAM console and create a user named migration-dest-user.
  • Attach the AmazonS3FullAccess policy to this user.
  • Create an access key for this user following the same steps above.
  • Download and save the CSV file securely.

What permissions does each user need?

The source account user needs S3 and IAM permissions because it will create the cross-account role later. The destination account user needs S3 permissions to write to the destination bucket and will also need STS permissions (which you will add in a later step).

Configure your named CLI profiles

Named profiles let you manage multiple AWS accounts from a single terminal. Instead of swapping credentials manually, you pass a --profile flag to tell the CLI which account to use.

  • Configure your source account profile by running this command:
aws configure --profile source

What will aws configure ask me?

The command will prompt you for four values. Enter them as follows:

  • AWS Access Key ID [None]: paste the access key ID from your source account CSV.
  • AWS Secret Access Key [None]: paste the secret access key from your source account CSV.
  • Default region name [None]: type us-east-1.
  • Default output format [None]: type json.
  • Now configure your destination account profile by running:
aws configure --profile destination
  • Enter the same four prompts, this time using the access key ID and secret from your destination account CSV.
  • For region, enter us-east-1.
  • For output format, enter json.

Before you run the verification commands, what do you think each one will return? Each profile points at a different AWS account, so the Account field should be different.

  • Verify your source profile by running:
aws sts get-caller-identity --profile source
  • Verify your destination profile by running:
aws sts get-caller-identity --profile destination

Each command returns a JSON object with Account, Arn, and UserId fields. The Account value should be different between the two outputs. That confirms your CLI is correctly pointing at two separate AWS accounts.

Getting an error about invalid credentials?

Make sure you pasted the correct access key and secret for each profile. Source credentials go in the source profile, destination credentials go in the destination profile.

If you see InvalidClientTokenId, double-check that you copied the full access key ID without extra spaces.

help me fix my AWS CLI profile configuration so get-caller-identity works

✔️ Awesome, I've got everything!

Great. Both profiles are verified and pointing at different accounts. You are ready to start creating S3 buckets.

ⓧ I'd like to double check the full config

Your config file (located at C:\Users\USERNAME\.aws\config on Windows) should look like this:

[profile source]
region = us-east-1
output = json

[profile destination]
region = us-east-1
output = json

Your credentials file (located at C:\Users\USERNAME\.aws\credentials) should have two sections with your own access key values:

[source]
aws_access_key_id = YOUR_SOURCE_ACCESS_KEY
aws_secret_access_key = YOUR_SOURCE_SECRET_KEY

[destination]
aws_access_key_id = YOUR_DEST_ACCESS_KEY
aws_secret_access_key = YOUR_DEST_SECRET_KEY

If anything looks off, run aws configure --profile source or aws configure --profile destination again to re-enter the values.

Your environment is ready. You have two AWS accounts wired up to your terminal. Next up, you will create the source data and upload it to S3.

Create Source Data and Upload to S3

Your tools and accounts are ready. Now you need actual data to migrate.

A migration without data is just configuration. In this step, you will create S3 buckets in both accounts and upload sample files to the source. These files give you something concrete to point at when the migration succeeds (or fails) in the next step.

In this step, get ready to:
  • Create a source S3 bucket and upload sample data files.
  • Create an empty destination S3 bucket in your second account.
  • Verify your files are accessible from the CLI.
Create the source S3 bucket

Every S3 bucket name must be globally unique across all AWS accounts. You will use a naming pattern that includes your name to avoid collisions.

  • Create the source bucket by running this command:
aws s3 mb s3://migration-source-[[YOURNAME="your name"]]-2026 --profile source

You should see make_bucket: s3://migration-source-yourname-2026 confirming the bucket was created.

What does this command do?

The s3 mb subcommand stands for "make bucket." The --profile source flag tells the CLI to use your source account credentials, so the bucket is created in that account.

Seeing a BucketAlreadyExists error?

S3 bucket names are globally unique across all AWS customers. If someone else already has this name, add extra characters (your initials, a few random digits) to make it unique. Re-run the command with the updated name.

help me fix a BucketAlreadyExists error when creating an S3 bucket

Create sample files and upload them

You need actual files in the source bucket so you can verify they arrive in the destination after migration. You will create three small text files locally, then upload the entire folder.

  • Move to your Desktop by running this command:
cd ~\Desktop
  • Create a folder called migration-data with three sample text files by running these commands:
mkdir migration-data
echo "Employee records batch 1 - source account" > migration-data\file1.txt
echo "Financial reports Q1 2026 - source account" > migration-data\file2.txt
echo "Project documentation archive - source account" > migration-data\file3.txt

What do these files represent?

These files simulate real migration data. In production, you would be moving databases, application logs, or media assets between accounts. The content does not matter here. What matters is having multiple objects to transfer.

  • Confirm the files exist by running:
ls migration-data

You should see file1.txt, file2.txt, and file3.txt listed in the output.

  • Upload the entire folder to your source bucket by running this command:
aws s3 cp migration-data/ s3://migration-source-[[YOURNAME="your name"]]-2026/migration-data/ --recursive --profile source

You should see three lines of output, each confirming a file was uploaded to S3.

What does --recursive do?

The --recursive flag tells the CLI to upload every file inside the folder, not just the folder reference itself. Without it, the command would skip the contents.

Upload failed or no files copied?

Make sure you are running the command from your Desktop (where the migration-data folder lives). The trailing / after the local path is important. Without it, the CLI may not find your files.

help me troubleshoot an S3 upload that shows no files copied

Create the destination bucket

The destination bucket lives in your second AWS account. This is where the migrated data will land.

  • Create the destination bucket by running this command:
aws s3 mb s3://migration-dest-[[YOURNAME="your name"]]-2026 --profile destination

You should see make_bucket: s3://migration-dest-yourname-2026 confirming the bucket was created in your destination account.

Why a separate bucket in a separate account?

In the real world, cross-account migrations happen when companies acquire other companies, split into separate business units, or move workloads between development and production accounts. Each account is an isolated security boundary.

Your two-account setup mimics this exact scenario. The source and destination have no shared permissions by default.

Before you verify, what do you expect to see if you list the contents of your source bucket?

  • Verify your source files are accessible by running this command:
aws s3 ls s3://migration-source-[[YOURNAME="your name"]]-2026/migration-data/ --profile source

You should see all three files listed with their sizes and timestamps. This confirms your source data is ready for migration.

Not seeing your files?

Double-check that the bucket name in your ls command matches exactly what you used in the mb command. Also verify you included /migration-data/ at the end of the S3 URI. Without it, you will see the prefix listing instead of file contents.

help me list the contents of my S3 bucket using the AWS CLI

Your source data is uploaded and your destination bucket is standing by. Next up, you will attempt to copy files across accounts and discover exactly why AWS blocks you.

Attempt the Cross-Account Copy (Hit the Wall)

Your source bucket has sample files and your destination bucket is ready. Everything is in place for the actual data migration between accounts.

The most intuitive approach is to simply copy files from one bucket to another using your CLI profiles. Let's try it and see what happens when two AWS accounts interact without any special permissions configured.

In this step, get ready to:
  • Run a cross-account S3 copy command using your destination profile.
  • Diagnose why the copy fails and identify what AWS needs to allow cross-account access.
Run the cross-account copy command

The simplest way to migrate data between accounts is a direct aws s3 cp from the source bucket to the destination bucket. Since you want to pull data INTO the destination account, you would naturally use the destination profile.

Before you run this command, what do you think will happen? Will the destination account be able to read files from the source account's bucket?

  • Attempt the cross-account copy by running this command:
aws s3 cp s3://[[SOURCE_BUCKET="migration-source-YOURNAME-2026"]]/migration-data/ s3://[[DEST_BUCKET="migration-dest-YOURNAME-2026"]]/migration-data/ --recursive --profile destination

The command fails. You see output containing fatal error: An error occurred (AccessDenied) in your terminal.

This is exactly what we expected. The destination account has zero permission to read the source account's bucket.

Why did this fail?

AWS accounts are completely isolated from each other by default. Your destination account's IAM user has no permission to touch anything in the source account.

It does not matter that both profiles are configured on the same machine. From AWS's perspective, these are two strangers. Neither a bucket policy nor an IAM role currently grants cross-account access.

Seeing a different error?

If you see NoSuchBucket instead, double-check that your bucket name matches exactly what you created in Step 2. Bucket names are case-sensitive and globally unique.

If you see InvalidAccessKeyId, your destination profile credentials may be misconfigured. Re-run aws configure --profile destination and re-enter your access key and secret key.

help me figure out why I'm not seeing an AccessDenied error on my cross-account S3 copy

Understand the two-sided trust model

The AccessDenied error reveals a fundamental rule of AWS security. Cross-account access requires BOTH sides to explicitly agree.

What is the two-sided trust model?

For one account to access another account's resources, two things must be true:

  • The source account (where the resource lives) must say: "I trust this other account to access my stuff." This is the trust policy.
  • The destination account (where the requester lives) must say: "My user is allowed to assume that trust." This is the permissions policy.

Right now, neither side has agreed to anything. The source bucket has no bucket policy or IAM role granting external access. The destination user has no permission to assume a cross-account role.

This is the pattern behind every cross-account AWS operation, not just S3. The mechanism that bridges this gap is STS AssumeRole.

  • Confirm the destination bucket is still empty by running:
aws s3 ls s3://[[DEST_BUCKET="migration-dest-YOURNAME-2026"]]/migration-data/ --profile destination

You should see no output, confirming that no files were copied. The failed command did not transfer anything.

Now you know exactly why a naive cross-account copy fails and what it takes to fix it. In the next step, you will build an IAM role in the source account that the destination account can assume, creating the two-sided trust bridge that makes the migration possible.

Build the Cross-Account IAM Role

In the previous step, the Access Denied error proved that AWS accounts are completely isolated by default. No amount of retrying will fix that. You need an explicit bridge between the two accounts.

That bridge is an IAM role. You will create a role in the source account that the destination account is allowed to assume. This two-sided trust model is the core pattern behind every cross-account operation in AWS.

In this step, get ready to:
  • Create an IAM role in the source account with a trust policy that trusts the destination account.
  • Attach a permissions policy granting the role read access to the source S3 bucket.
  • Grant the destination IAM user permission to call AssumeRole on the new role.
Create the trust policy and IAM role in the source account

A trust policy declares WHO is allowed to assume a role. You need to tell the source account's role that the destination account is a trusted principal.

  • First, retrieve both account IDs so you have them ready. Run these commands:
aws sts get-caller-identity --profile source
aws sts get-caller-identity --profile destination

Note the 12-digit Account value from each output. You will use both in the policy files below.

  • Create a file called trust-policy.json with the following content:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[[DEST_ACCOUNT_ID="your 12-digit destination account ID"]]:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

What does this trust policy do?

The Principal field names the destination account's root ARN. This tells AWS that any IAM entity in that account is allowed to request temporary credentials for this role.

The Action field restricts what the principal can do: only sts:AssumeRole. The trust policy is one side of the handshake. The destination account still needs its own permission to complete it.

  • Create the IAM role using the trust policy by running this command:
aws iam create-role --role-name S3MigrationRole --assume-role-policy-document file://trust-policy.json --profile source

You should see JSON output containing a Role object with fields like RoleName, Arn, and CreateDate. The Arn looks like arn:aws:iam::SOURCE_ACCOUNT_ID:role/S3MigrationRole.

Seeing a MalformedPolicyDocument error?

Double check that your JSON is valid. A missing comma, extra bracket, or mismatched quote will cause this error. Make sure the Principal value is the full ARN including arn:aws:iam:: and :root at the end.

help me fix a MalformedPolicyDocument error when creating my IAM role

Attach S3 read permissions to the role

The trust policy controls WHO can assume the role. Now you need a separate permissions policy that controls WHAT the role can do once assumed.

  • Create a file called s3-access-policy.json with the following content:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::[[SOURCE_BUCKET="your source bucket name e.g. migration-source-alex-2026"]]/*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::[[SOURCE_BUCKET="your source bucket name e.g. migration-source-alex-2026"]]"
    }
  ]
}

Why two separate statements?

S3 permissions work at two levels. s3:ListBucket operates on the bucket itself (no trailing /*). s3:GetObject operates on the objects inside the bucket (requires /* at the end of the resource ARN).

Together, these let the role list the bucket contents and download each file. This is the minimum needed for a read-only migration.

  • Attach this policy to the role by running:
aws iam put-role-policy --role-name S3MigrationRole --policy-name S3ReadAccess --policy-document file://s3-access-policy.json --profile source

This command produces no output on success. If you see no error, the policy is attached.

Getting a NoSuchEntity error?

This means the role S3MigrationRole was not found. Confirm you are using --profile source (the role lives in the source account). Also check that the previous create-role command succeeded.

help me troubleshoot a NoSuchEntity error when attaching a policy to my IAM role

Grant the destination user permission to assume the role

Cross-account trust requires both sides to agree. The source account now trusts the destination, but the destination user also needs explicit permission to call sts:AssumeRole.

  • Create a file called assume-role-policy.json with the following content:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::[[SOURCE_ACCOUNT_ID="your 12-digit source account ID"]]:role/S3MigrationRole"
    }
  ]
}

What does this policy do?

This grants the destination IAM user permission to call STS AssumeRole, but only on the specific S3MigrationRole ARN. This is least-privilege access. The user cannot assume any other role in any other account.

  • Attach this policy to your destination IAM user by running:
aws iam put-user-policy --user-name [[DEST_USERNAME="your destination IAM username"]] --policy-name AllowAssumeS3Migration --policy-document file://assume-role-policy.json --profile destination

No output means success. Both sides of the trust handshake are now in place.

Before you run the verification command below, what do you think will happen? Will the destination profile be able to assume the source role and receive temporary credentials?

  • Verify the full trust chain works by running:
aws sts assume-role --role-arn arn:aws:iam::[[SOURCE_ACCOUNT_ID="your 12-digit source account ID"]]:role/S3MigrationRole --role-session-name test-session --profile destination

You should see a JSON response containing Credentials with three fields: AccessKeyId, SecretAccessKey, and SessionToken. These are the temporary credentials that prove the cross-account trust is working.

Getting an AccessDenied on AssumeRole?

This means one side of the handshake is incomplete. Check these in order:

  • Verify the trust policy's Principal ARN matches your destination account's 12-digit ID exactly (no typos, no extra spaces).
  • Verify the assume-role-policy.json Resource ARN matches your source account's 12-digit ID and says role/S3MigrationRole (case-sensitive).
  • Confirm you ran put-user-policy with --profile destination (not source).

help me debug an AccessDenied error when trying to assume my cross-account IAM role

The bridge between your two accounts is built and verified. Next up, you will use this role to execute the migration that failed in Step 3 and watch the files flow across.

Execute the Migration and Verify

In the previous step, you built a cross-account IAM role with a trust policy and a permissions policy. The bridge between your two accounts now exists.

Now you get to use it. In this step, you will configure a CLI profile that automatically assumes the cross-account role, run the same migration command that failed in Step 3, and watch it succeed this time.

In this step, get ready to:
  • Configure a CLI profile that automatically assumes the cross-account role.
  • Execute the S3 migration using the cross-account profile.
  • Verify the files arrived in the destination and the source remains intact.
Configure the cross-account CLI profile

The AWS CLI can automatically call AssumeRole behind the scenes when you set up a profile with a role_arn and a source_profile. This means you will not need to manually copy temporary credentials every time you want to act as the cross-account role.

  • Open your AWS CLI config file by running this command:
notepad "$env:USERPROFILE\.aws\config"
  • In the file that opens, you should see your existing [profile source] and [profile destination] sections.
  • Add the following new profile section at the bottom of the file, below the existing profiles:
[profile cross-account]
role_arn = arn:aws:iam::[[SOURCE_ACCOUNT_ID="your source account ID"]]:role/S3MigrationRole
source_profile = destination
region = us-east-1

What does this profile do?

  • role_arn tells the CLI which cross-account role to assume. This is the S3MigrationRole you created in the source account.
  • source_profile = destination tells the CLI to use the destination account's credentials as the starting identity. The CLI uses those credentials to call sts:AssumeRole on your behalf.
  • The result is that any command you run with --profile cross-account automatically gains the permissions of S3MigrationRole in the source account.
  • Save the file in Notepad (Ctrl+S) and close it.

Your full config file should now look like this:

✔️ Awesome, I've got everything!

Great. Double check you have saved the file before continuing.

ⓧ I'd like to double check the full code

[profile source]
region = us-east-1
output = json

[profile destination]
region = us-east-1
output = json

[profile cross-account]
role_arn = arn:aws:iam::[[SOURCE_ACCOUNT_ID="your source account ID"]]:role/S3MigrationRole
source_profile = destination
region = us-east-1

Not sure what your source account ID is?

Run aws sts get-caller-identity --profile source in your terminal. The Account field in the output is the 12-digit number you need for the role_arn.

help me find my AWS account ID for the cross-account profile

Run the migration

This is the same copy command that failed with Access Denied in Step 3. The only difference is the profile flag.

  • Before you run this, predict what you think will happen. Will it succeed this time, or fail again? Why?
  • Run the cross-account migration by executing this command:
aws s3 cp s3://migration-source-[[YOURNAME="your name"]]-2026/migration-data/ s3://migration-dest-[[YOURNAME="your name"]]-2026/migration-data/ --recursive --profile cross-account

This time it succeeds. You should see output showing each file being copied, with no errors.

Why did it work this time?

When you use --profile cross-account, the CLI automatically calls sts:AssumeRole using your destination credentials. It receives temporary credentials that carry the permissions of S3MigrationRole.

Those temporary credentials have s3:GetObject and s3:ListBucket on the source bucket. The CLI reads from the source and writes to the destination (which the destination account already owns). Both sides of the trust model are satisfied.

Still seeing Access Denied?

Check these common causes:

  • Confirm the role_arn in your config uses the source account's 12-digit ID, not the destination account's.
  • Verify source_profile is set to destination (the destination account is the one assuming the role).
  • Ensure your bucket name in the command exactly matches the bucket you created in Step 2.

help me fix Access Denied on my cross-account S3 migration

Verify the migration is complete

A successful migration means the files exist in the destination AND the source is still intact. The aws s3 cp command copies data. It does not move or delete the originals.

  • List the destination bucket contents by running:
aws s3 ls s3://migration-dest-[[YOURNAME="your name"]]-2026/migration-data/ --profile destination

You should see all 3 of your sample text files listed. These are the files that just arrived from the source account.

  • Confirm the source data is still intact by running:
aws s3 ls s3://migration-source-[[YOURNAME="your name"]]-2026/migration-data/ --profile source

You should see the same 3 files still present. The source bucket is unchanged.

What does this prove?

You have just moved data across an AWS account boundary using the same trust model that powers every cross-account operation in AWS. The pattern (trust policy + permissions policy + AssumeRole) works identically for DynamoDB, Lambda, KMS, and any other service.

Secret mission

Set Up Cross-Account S3 Replication

A one-time copy is useful, but real migrations often need ongoing sync. Can you configure S3 Replication so new objects in the source automatically appear in the destination without any manual intervention?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses S3 storage for a few small text files and IAM resources, which cost fractions of a cent total. IAM roles and policies are free.

Resources you used:

  • S3 bucket migration-source-YOURNAME-2026 (source account) with sample files and replication configuration.
  • S3 bucket migration-dest-YOURNAME-2026 (destination account) with migrated and replicated files.
  • IAM role S3MigrationRole in the source account with inline policy S3ReadAccess.
  • IAM role S3ReplicationRole in the source account (Secret Mission).
  • Inline user policy AllowAssumeS3Migration on the destination account IAM user.
  • Local files: migration-data/ folder, JSON policy files, and AWS CLI profile entries.

Keep everything running

No action needed. Choose this if you plan to continue experimenting with cross-account operations or do a follow-up project. S3 storage of a few text files costs effectively nothing, and IAM resources are free.

  • Your CLI profiles remain configured so you can run cross-account commands anytime.
  • Replication stays active if you completed the Secret Mission. New objects in the source bucket will continue to replicate automatically.

Pause - I'll come back to this later

There is nothing to pause for this project. S3 storage of a few text files costs a fraction of a cent per month, and IAM resources incur no charge. You can safely leave everything as-is until you return.

  • If you completed the Secret Mission and want to stop replication, remove the replication configuration by running:
  • This stops new objects from replicating but keeps all existing files in both buckets.
aws s3api delete-bucket-replication --bucket migration-source-YOURNAME-2026 --profile source

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow these steps in order.

  • Delete the replication configuration from the source bucket (if you completed the Secret Mission):
aws s3api delete-bucket-replication --bucket migration-source-YOURNAME-2026 --profile source
  • Delete both S3 buckets (the --force flag empties the bucket before removing it):
aws s3 rb s3://migration-source-YOURNAME-2026 --force --profile source
aws s3 rb s3://migration-dest-YOURNAME-2026 --force --profile destination
  • Remove the inline policy and then delete the S3MigrationRole in the source account:
aws iam delete-role-policy --role-name S3MigrationRole --policy-name S3ReadAccess --profile source
aws iam delete-role --role-name S3MigrationRole --profile source
  • Remove the inline policy and then delete the S3ReplicationRole in the source account (Secret Mission):
aws iam delete-role-policy --role-name S3ReplicationRole --policy-name S3ReplicationPolicy --profile source
aws iam delete-role --role-name S3ReplicationRole --profile source
  • Remove the inline user policy from the destination account IAM user:
aws iam delete-user-policy --user-name YOUR_DEST_USER --policy-name AllowAssumeS3Migration --profile destination
  • Delete the local project files:
cd ~/Desktop
rmdir /s /q migration-data
del trust-policy.json s3-access-policy.json assume-role-policy.json
  • Remove the cross-account profile section from your ~/.aws/config file (located at %UserProfile%\.aws\config on Windows). Open the file in a text editor and delete the [profile cross-account] block.
  • Optionally remove the source and destination profiles from ~/.aws/config and ~/.aws/credentials if you no longer need access to those accounts from the CLI.

Nice Work!

Nice Work!

Nice work! You just migrated data between two separate AWS accounts using a cross-account IAM role and the AWS CLI.

You've learned how to:

  • Diagnose a cross-account Access Denied error and fix it by building the two-sided IAM trust model (trust policy + permissions policy).
  • Execute a successful S3 data migration between accounts using STS AssumeRole to bridge the account boundary.
  • Configure a reusable CLI profile that automatically assumes a cross-account role, turning future migrations into a one-liner.
  • Secret Mission: Set up S3 Replication for automatic ongoing data sync between accounts.

Ready to quiz yourself?