Skip to content

Commit

Permalink
Merge pull request #7 from rhythmictech/add-replication
Browse files Browse the repository at this point in the history
Add bucket replication
  • Loading branch information
kmackowick authored Jul 14, 2022
2 parents e2f9ee1 + a0ab3a0 commit 3628b97
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 15 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@

# .tfvars files
*.tfvars

# macs
.DS_Store

# temp folders
tmp

.terraform.lock.hcl
29 changes: 25 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
exclude: ".terraform"
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.50.0
rev: v1.73.0
hooks:
- id: terraform_docs
always_run: true
args:
- --args=--sort-by-required
- id: terraform_fmt
- id: terraform_tflint
alias: terraform_tflint_nocreds
Expand Down Expand Up @@ -36,8 +34,29 @@ repos:
verbose: true
files: \.tf(vars)?$
exclude: examples
- id: tflock
name: provider_locks
entry: |
bash -c '
AWS_DEFAULT_REGION=us-east-1
declare -a DIRS
for FILE in "$@"
do
DIRS+=($(dirname "$FILE"))
done
for DIR in $(printf "%s\n" "${DIRS[@]}" | sort -u)
do
cd $(dirname "$FILE")
terraform providers lock -platform=windows_amd64 -platform=darwin_amd64 -platform=linux_amd64
cd ..
done
'
language: system
verbose: true
files: \.tf(vars)?$
exclude: examples
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
rev: v4.3.0
hooks:
- id: check-case-conflict
- id: check-json
Expand Down Expand Up @@ -66,3 +85,5 @@ repos:
args:
- --markdown-linebreak-ext=md
exclude: README.md
ci:
skip: [terraform_docs, terraform_fmt, terraform_tflint, terraform_tfsec, tflock]
2 changes: 1 addition & 1 deletion .terraform-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
latest:^0.13
latest:^1.1
34 changes: 25 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# terraform-aws-helmrepo

[![tflint](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/tflint/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Atflint+event%3Apush+branch%3Amain)
[![tfsec](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/tfsec/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Atfsec+event%3Apush+branch%3Amain)
[![yamllint](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/yamllint/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Ayamllint+event%3Apush+branch%3Amain)
[![misspell](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/misspell/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Amisspell+event%3Apush+branch%3Amain)
[![pre-commit-check](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/pre-commit-check/badge.svg?branch=main&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Apre-commit-check+event%3Apush+branch%3Amain)
<a href="https://twitter.com/intent/follow?screen_name=RhythmicTech"><img src="https://img.shields.io/twitter/follow/RhythmicTech?style=social&logo=twitter" alt="follow on Twitter"></a>
[![tflint](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/tflint/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Atflint+event%3Apush+branch%3Amaster)
[![tfsec](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/tfsec/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Atfsec+event%3Apush+branch%3Amaster)
[![yamllint](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/yamllint/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Ayamllint+event%3Apush+branch%3Amaster)
[![misspell](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/misspell/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Amisspell+event%3Apush+branch%3Amaster)
[![pre-commit-check](https://github.com/rhythmictech/terraform-aws-helmrepo/workflows/pre-commit-check/badge.svg?branch=master&event=push)](https://github.com/rhythmictech/terraform-aws-helmrepo/actions?query=workflow%3Apre-commit-check+event%3Apush+branch%3Amaster)

Create an S3 bucket intended to serve as a Helm repo. Configures basic encryption and supports sharing the bucket across many accounts.

Expand All @@ -15,19 +13,23 @@ module {
source = "rhythmictech/helmrepo/aws"
}
```

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.12.19 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.0.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 4.0 |
| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.3 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.0.0 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | 4.22.0 |
| <a name="provider_aws.destination"></a> [aws.destination](#provider\_aws.destination) | 4.22.0 |
| <a name="provider_random"></a> [random](#provider\_random) | 3.3.2 |

## Modules

Expand All @@ -37,10 +39,21 @@ No modules.

| Name | Type |
|------|------|
| [aws_iam_policy.replication_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_policy_attachment.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy_attachment) | resource |
| [aws_iam_role.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_s3_bucket.destination](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
| [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
| [aws_s3_bucket_policy.destination](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
| [aws_s3_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
| [aws_s3_bucket_public_access_block.dest_block_public_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
| [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
| [aws_s3_bucket_replication_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource |
| [random_id.replication](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.destination](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.replication_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.replication_policy_doc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_region.region](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |

Expand All @@ -50,6 +63,9 @@ No modules.
|------|-------------|------|---------|:--------:|
| <a name="input_allowed_account_ids"></a> [allowed\_account\_ids](#input\_allowed\_account\_ids) | List of AWS account IDs to grant read-only access to the repo. Due to how policies are constructed, there's effectively a limit of about 9 accounts. | `list(string)` | `[]` | no |
| <a name="input_allowed_account_ids_write"></a> [allowed\_account\_ids\_write](#input\_allowed\_account\_ids\_write) | List of AWS account IDs to grant write access to the repo. Due to how policies are constructed, there's effectively a limit of about 9 accounts. | `list(string)` | `[]` | no |
| <a name="input_dest_logging_bucket"></a> [dest\_logging\_bucket](#input\_dest\_logging\_bucket) | S3 bucket name to log bucket access requests to (optional) | `string` | `null` | no |
| <a name="input_dest_logging_bucket_prefix"></a> [dest\_logging\_bucket\_prefix](#input\_dest\_logging\_bucket\_prefix) | S3 bucket prefix to log bucket access requests to (optional). If blank but a `logging_bucket` is specified, this will be set to the name of the bucket | `string` | `null` | no |
| <a name="input_dest_region"></a> [dest\_region](#input\_dest\_region) | Region to replicate repo bucket to (omit to disable replication) | `string` | `""` | no |
| <a name="input_logging_bucket"></a> [logging\_bucket](#input\_logging\_bucket) | S3 bucket name to log bucket access requests to (optional) | `string` | `null` | no |
| <a name="input_logging_bucket_prefix"></a> [logging\_bucket\_prefix](#input\_logging\_bucket\_prefix) | S3 bucket prefix to log bucket access requests to (optional). If blank but a `logging_bucket` is specified, this will be set to the name of the bucket | `string` | `null` | no |
| <a name="input_name"></a> [name](#input\_name) | Bucket name for the helm repo. Specify to control the exact name of the bucket, otherwise use `name_suffix` | `string` | `null` | no |
Expand Down
83 changes: 83 additions & 0 deletions iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
data "aws_iam_policy_document" "replication_assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["s3.amazonaws.com"]
}
}
}

resource "aws_iam_role" "replication" {
count = var.dest_region != "" ? 1 : 0

name_prefix = "replication"
assume_role_policy = data.aws_iam_policy_document.replication_assume_role.json
tags = var.tags
}

data "aws_iam_policy_document" "replication_policy_doc" {
count = var.dest_region != "" ? 1 : 0
statement {
effect = "Allow"
actions = [
"s3:GetReplicationConfiguration",
"s3:ListBucket"
]

resources = [
aws_s3_bucket.this.arn
]

}

statement {
effect = "Allow"
actions = [
"s3:GetObjectVersion",
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersionAcl"
]

resources = [
"${aws_s3_bucket.this.arn}/*"
]

}

statement {
effect = "Allow"
actions = [
"s3:ReplicateObject",
"s3:ReplicateDelete"
]

resources = [
"${aws_s3_bucket.destination[0].arn}/*"
]

}

}

resource "aws_iam_policy" "replication_policy" {
count = var.dest_region != "" ? 1 : 0

name_prefix = "replication-policy"
policy = data.aws_iam_policy_document.replication_policy_doc[0].json
}

resource "aws_iam_policy_attachment" "replication" {
count = var.dest_region != "" ? 1 : 0

name = "replication"
roles = [aws_iam_role.replication[0].name]
policy_arn = aws_iam_policy.replication_policy[0].arn
}

resource "random_id" "replication" {
count = var.dest_region != "" ? 1 : 0
byte_length = 32
}
150 changes: 150 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
provider "aws" {
alias = "destination"
region = var.dest_region
}

data "aws_caller_identity" "current" {
}

Expand All @@ -11,6 +16,11 @@ locals {
bucket = var.logging_bucket
prefix = var.logging_bucket_prefix != null ? var.logging_bucket_prefix : local.bucket_name
}]

dest_logging_map = var.dest_logging_bucket == null ? [] : [{
bucket = var.dest_logging_bucket
prefix = var.dest_logging_bucket_prefix != null ? var.dest_logging_bucket_prefix : "${local.bucket_name}-replica"
}]
}

# This bucket uses a dynamic block to generate logging. If users do not wish to log,
Expand Down Expand Up @@ -119,3 +129,143 @@ resource "aws_s3_bucket_policy" "this" {
bucket = aws_s3_bucket.this.id
policy = data.aws_iam_policy_document.this.json
}

########################################
# Source replication configuration
########################################
resource "aws_s3_bucket_replication_configuration" "this" {
count = var.dest_region != "" ? 1 : 0

role = aws_iam_role.replication[0].arn
bucket = aws_s3_bucket.this.id

rule {
id = random_id.replication[0].b64_std
priority = 0

delete_marker_replication {
status = "Enabled"
}

filter {
prefix = ""
}

status = "Enabled"

destination {
bucket = aws_s3_bucket.destination[0].arn
}
}
}

########################################
# Replicated bucket
########################################
#tfsec:ignore:AWS002
resource "aws_s3_bucket" "destination" {
count = var.dest_region != "" ? 1 : 0
provider = aws.destination

bucket = "${local.bucket_name}-replica"
acl = "private"
tags = var.tags

dynamic "logging" {
for_each = local.dest_logging_map

content {
target_bucket = logging.value.bucket
target_prefix = logging.value.prefix
}
}

versioning {
enabled = true
}

server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

}

resource "aws_s3_bucket_public_access_block" "dest_block_public_access" {
count = var.dest_region != "" ? 1 : 0
provider = aws.destination

bucket = aws_s3_bucket.destination[0].id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

data "aws_iam_policy_document" "destination" {
count = var.dest_region != "" ? 1 : 0
provider = aws.destination
dynamic "statement" {
for_each = var.allowed_account_ids

content {

sid = "Allow cross-account access to list objects (${statement.value})"
actions = ["s3:ListBucket"]
effect = "Allow"
resources = [aws_s3_bucket.destination[0].arn]

principals {
identifiers = ["arn:aws:iam::${statement.value}:root"]
type = "AWS"
}
}
}

dynamic "statement" {
# Remove accounts with write access from this statement to keep policy size down
for_each = setsubtract(var.allowed_account_ids, var.allowed_account_ids_write)

content {
sid = "Allow Cross-account read-only access (${statement.value})"
actions = ["s3:GetObject*"]
effect = "Allow"
resources = ["${aws_s3_bucket.destination[0].arn}/*"]

principals {
identifiers = ["arn:aws:iam::${statement.value}:root"]
type = "AWS"
}
}
}

dynamic "statement" {
for_each = var.allowed_account_ids_write

content {
sid = "Allow Cross-account write access (${statement.value})"
actions = [
"s3:GetObject*",
"s3:PutObject*"
]
effect = "Allow"
resources = ["${aws_s3_bucket.destination[0].arn}/*"]

principals {
identifiers = ["arn:aws:iam::${statement.value}:root"]
type = "AWS"
}
}
}
}

resource "aws_s3_bucket_policy" "destination" {
count = var.dest_region != "" ? 1 : 0
provider = aws.destination

bucket = aws_s3_bucket.destination[0].id
policy = data.aws_iam_policy_document.destination[0].json
}
Loading

0 comments on commit 3628b97

Please sign in to comment.