AWS bucket replication lab

Cloud Data and Risk Management Lab

Goals:

Lab Outline

S3 bucket versioning and replication

To test:

The “permission magic” under the hood:

Preparation

Create terraform configuration

cd
cd terraform
mkdir s3
cd s3
nano main.tf

!! REMEMBER to replace with your group name!

Example: “group10”


provider "aws" {
  region = "ap-southeast-1"
}

# Define the remote backend for the terraform state database

terraform {
  backend "s3" {
      bucket = "nsrc-noc"
      key    = "terraform/deploy-lab/<group_name>/s3_replication_terraform_state_file"
      region = "ap-southeast-1"
  }
  required_providers {
    aws  = {
      source = "hashicorp/aws"
    }
  }

}


# Create source bucket
resource "aws_s3_bucket" "source_bucket" {
  bucket = "<group_name>-source"
  force_destroy = true
}

resource "aws_s3_bucket_versioning" "source" {
  bucket = aws_s3_bucket.source_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}

# Create destination bucket
resource "aws_s3_bucket" "destination_bucket" {
  bucket = "<group_name>-destination"
  force_destroy = true
}

resource "aws_s3_bucket_versioning" "destination" {
  bucket = aws_s3_bucket.destination_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}


resource "aws_s3_bucket_replication_configuration" "replication" {
  bucket = aws_s3_bucket.source_bucket.id

  ## this role has already been defined for you
  ## we will review later

  role = "arn:aws:iam::058264411872:role/service-role/s3crr_role_for_nsrc-noc" # Specify ARN of IAM role for replication

  rule {
    id     = "rule-id"
    status = "Enabled"

    destination {
      bucket = aws_s3_bucket.destination_bucket.arn
    }

  }
  depends_on = [aws_s3_bucket_versioning.source]
}

Apply terraform configuration

terraform init

then

terraform plan

!!! NOTE: Remember review the plan carefully!

You should see an output similar to this:

Terraform will perform the following actions:

  # aws_s3_bucket.destination_bucket will be created
  + resource "aws_s3_bucket" "destination_bucket" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "noc_destination"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)
    }

  # aws_s3_bucket.source_bucket will be created
  + resource "aws_s3_bucket" "source_bucket" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "noc_source"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)
    }

# aws_s3_bucket_replication_configuration.replication will be created
  + resource "aws_s3_bucket_replication_configuration" "replication" {
      + bucket = (known after apply)
      + id     = (known after apply)
      + role   = "arn:aws:iam::058264411872:role/service-role/s3crr_role_for_nsrc-noc"

      + rule {
          + id     = "rule-id"
          + status = "Enabled"

          + destination {
              + bucket = (known after apply)
            }
        }
    }

  # aws_s3_bucket_versioning.destination will be created
  + resource "aws_s3_bucket_versioning" "destination" {
      + bucket = (known after apply)
      + id     = (known after apply)

      + versioning_configuration {
          + mfa_delete = (known after apply)
          + status     = "Enabled"
        }
    }

  # aws_s3_bucket_versioning.source will be created
  + resource "aws_s3_bucket_versioning" "source" {
      + bucket = (known after apply)
      + id     = (known after apply)

      + versioning_configuration {
          + mfa_delete = (known after apply)
          + status     = "Enabled"
        }
    }

Plan: 5 to add, 0 to change, 0 to destroy.

Let’s apply the terraform plan

terraform apply

Let’s see what terraform created

terraform state list

you should see something like this:

aws_s3_bucket.destination_bucket
aws_s3_bucket.source_bucket
aws_s3_bucket_replication_configuration.replication
aws_s3_bucket_versioning.destination
aws_s3_bucket_versioning.source

Let’s take a look at the replication configuration we created:

terraform state show aws_s3_bucket_replication_configuration.replication

You should see something like this:

resource "aws_s3_bucket_replication_configuration" "replication" {
    bucket = "noc-source"
    id     = "noc-source"
    role   = "arn:aws:iam::058264411872:role/service-role/s3crr_role_for_nsrc-noc"

    rule {
        id       = "rule-id"
        priority = 0
        status   = "Enabled"

        destination {
            bucket = "arn:aws:s3:::noc-destination"
        }
    }
}

The output is easy to understand:

Verify replication works

Now, let’s verify if our replication setup works as intended!

Note that we could also use the AWS cli to check that what we created and uploaded to the “source” made it the the “destination”

Example: “group10”

aws s3 ls s3://<group_name>-source/<folder>/
aws s3 ls s3://<group_name>-destination/<folder>/

Example:

aws s3 ls s3://noc-source/test-replication/
2024-02-22 01:11:43          0
2024-02-22 01:12:08     270592 cloud-deployment.pdf
2024-02-22 01:13:59      62387 public-cloud-management.odp


aws s3 ls s3://noc-destination/test-replication/
2024-02-22 01:11:43          0
2024-02-22 01:12:08     270592 cloud-deployment.pdf
2024-02-22 01:13:59      62387 public-cloud-management.odp

If you can see something like that, you successfully completed the exercise. Congrats!!

Cleanup

Now let’s cleanup the buckets we created, to test terraform removing resources (end of resource lifecycle)

terraform destroy

Watch the destruction process. Once completed, if you run

terraform plan

The output should look like terraform is planning to create the resources. That means it removed the resources from both AWS and the terraform state database.

In other words, the resources were destroyed

Optional / Reference: Examine the role

You can find the role which was granted to the source S3 bucket (to allow it to replicate to another S3 bucket) under IAM Roles in the AWS web interface. With a little work, you can also extract the information from aws cli.

# 1. List the attached managed policies for the role
aws iam list-attached-role-policies --role-name s3crr_role_for_nsrc-noc
{
    "AttachedPolicies": [
        {
            "PolicyName": "s3crr_for_nsrc-noc_80e96a",
            "PolicyArn": "arn:aws:iam::058264411872:policy/service-role/s3crr_for_nsrc-noc_80e96a"
        }
    ]
}

# 2. For each attached managed policy, get the default policy version
aws iam get-policy --policy-arn arn:aws:iam::058264411872:policy/service-role/s3crr_for_nsrc-noc_80e96a
{
    "Policy": {
        "PolicyName": "s3crr_for_nsrc-noc_80e96a",
        "PolicyId": "ANPAQ3EGUL3QMB2B3ZWVW",
        "Arn": "arn:aws:iam::058264411872:policy/service-role/s3crr_for_nsrc-noc_80e96a",
        "Path": "/service-role/",
        "DefaultVersionId": "v2",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2024-02-22T08:36:47+00:00",
        "UpdateDate": "2024-02-22T08:43:43+00:00",
        "Tags": []
    }
}

# 3. Get the JSON policy document of the default version
aws iam get-policy-version --policy-arn arn:aws:iam::058264411872:policy/service-role/s3crr_for_nsrc-noc_80e96a --version-id v2
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": [
                        "s3:ListBucket",
                        "s3:GetReplicationConfiguration",
                        "s3:GetObjectVersionForReplication",
                        "s3:GetObjectVersionAcl",
                        "s3:GetObjectVersionTagging",
                        "s3:GetObjectRetention",
                        "s3:GetObjectLegalHold"
                    ],
                    "Effect": "Allow",
                    "Resource": [
                        "arn:aws:s3:::*",
                        "arn:aws:s3:::*/*"
                    ]
                },
                {
                    "Action": [
                        "s3:ReplicateObject",
                        "s3:ReplicateDelete",
                        "s3:ReplicateTags",
                        "s3:ObjectOwnerOverrideToBucketOwner"
                    ],
                    "Effect": "Allow",
                    "Resource": [
                        "arn:aws:s3:::*/*",
                        "arn:aws:s3:::*/*"
                    ]
                }
            ]
        },
        "VersionId": "v2",
        "IsDefaultVersion": true,
        "CreateDate": "2024-02-22T08:43:43+00:00"
    }
}

# 4. List inline policies embedded directly in the role (not managed policies)
aws iam list-role-policies --role-name s3crr_role_for_nsrc-noc
{
    "PolicyNames": []
}

# 5. If there were any, get the inline policy document for each inline policy name
aws iam get-role-policy --role-name <role_name> --policy-name <policy_name>