diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..477f0a5 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,4 @@ +FROM mcr.microsoft.com/devcontainers/base:bookworm + +# Install necessary dependencies +RUN apt-get update -y && rm -rf /var/lib/apt/lists/* diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..0dcbd3d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +{ + "name": "Terraform Development Container", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/terraform:1": { + "version": "latest" + }, + "ghcr.io/dhoeric/features/terraform-docs:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "hashicorp.terraform" + ] + } + }, + "postCreateCommand": "", + "remoteUser": "vscode" +} diff --git a/.github/workflows/commitmsg-conform.yml b/.github/workflows/commitmsg-conform.yml new file mode 100644 index 0000000..b8fe052 --- /dev/null +++ b/.github/workflows/commitmsg-conform.yml @@ -0,0 +1,11 @@ +name: Commit Message Conformance +on: + pull_request: {} +permissions: + statuses: write + checks: write + contents: read + pull-requests: read +jobs: + commitmsg-conform: + uses: tfstack/actions/.github/workflows/commitmsg-conform.yml@main diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml new file mode 100644 index 0000000..34ce215 --- /dev/null +++ b/.github/workflows/markdown-lint.yml @@ -0,0 +1,11 @@ +name: Markdown Lint +on: + pull_request: {} +permissions: + statuses: write + checks: write + contents: read + pull-requests: read +jobs: + markdown-lint: + uses: tfstack/actions/.github/workflows/markdown-lint.yml@main diff --git a/.github/workflows/validate-devschema.yml b/.github/workflows/validate-devschema.yml new file mode 100644 index 0000000..9987b9d --- /dev/null +++ b/.github/workflows/validate-devschema.yml @@ -0,0 +1,26 @@ +name: validate-devschema + +on: + pull_request: + paths: + - '**/devcontainer.json' + push: + branches: + - main + paths: + - '**/devcontainer.json' + +jobs: + validate-schema: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Validate Devcontainer Schema + uses: actionsforge/actions-validate-devschema@v1 + with: + schema: "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json" + data: ".devcontainer/devcontainer.json" + verbose: false + python-version: "3.13" diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..ec56e95 --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.84.0" + constraints = "5.84.0" + hashes = [ + "h1:dwpeFUdcxgXVAc0JSqO57xf0/r2qOBLPloombCQWFz8=", + "zh:078f77438aba6ec8bf9154b7d223e5c71c48d805d6cd3bcf9db0cc1e82668ac3", + "zh:1f6591ff96be00501e71b792ed3a5a14b21ff03afec9a1c4a3fd9300e6e5b674", + "zh:2ab694e022e81dd74485351c5836148a842ed71cf640664c9d871cb517b09602", + "zh:33c8ccb6e3dc496e828a7572dd981366c6271075c1189f249b9b5236361d7eff", + "zh:6f31068ebad1d627e421c72ccdaafe678c53600ca73714e977bf45ff43ae5d17", + "zh:7488623dccfb639347cae66f9001d39cf06b92e8081975235a1ac3a0ac3f44aa", + "zh:7f042b78b9690a8725c95b91a70fc8e264011b836605bcc342ac297b9ea3937d", + "zh:88b56ac6c7209dc0a775b79975a371918f3aed8f015c37d5899f31deff37c61a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a1979ba840d704af0932f8de5f541cbb4caa9b6bbd25ed552a24e6772175ba07", + "zh:b058c0533dae580e69d1adbc1f69e6a80632374abfc10e8634d06187a108e87b", + "zh:c88610af9cf957f8dcf4382e0c9ca566ef10e3290f5de01d4d90b2d81b078aa8", + "zh:e9562c055a2247d0c287772b55abef468c79f8d66a74780fe1c5e5dae1a284a9", + "zh:f7a7c71d28441d925a25c08c4485c015b2d9f0338bc9707443e91ff8e161d3d9", + "zh:fee533e81976d0900aa6fa443dc54ef171cbd901847f28a6e8edb1d161fa6fde", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.3" + hashes = [ + "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..8d4eadd --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + "recommendations": [ + "davidanson.vscode-markdownlint", + "eamodio.gitlens", + "esbenp.prettier-vscode", + "foxundermoon.shell-format", + "Gruntfuggly.todo-tree", + "hashicorp.terraform", + "mhutchie.git-graph", + "streetsidesoftware.code-spell-checker", + "usernamehw.errorlens", + "vscode-icons-team.vscode-icons" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..664e326 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,50 @@ +{ + "files.associations": { + "*.dockerfile": "dockerfile", + "*.sh.tpl": "shellscript", + "docker-compose*.yml": "yaml", + "Dockerfile*": "dockerfile" + }, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "__debug_bin": true, + "vendor/": true, + "go.sum": true + }, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "files.insertFinalNewline": true, + "remote.extensionKind": { + "ms-azuretools.vscode-docker": "ui", + "ms-vscode-remote.remote-containers": "ui" + }, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "prettier.requireConfig": true, + "workbench.iconTheme": "vscode-icons", + "[css]": { + "editor.defaultFormatter": "vscode.css-language-features", + "editor.foldingStrategy": "indentation" + }, + "[dockerfile]": { + "editor.defaultFormatter": "ms-azuretools.vscode-docker" + }, + "[html]": { + "editor.defaultFormatter": "vscode.html-language-features" + }, + "[json]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[shellscript]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + }, + "[terraform]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "hashicorp.terraform" + } +} diff --git a/README.md b/README.md index b884f8a..9a95ac4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,70 @@ # terraform-aws-s3 + Terraform module to create an S3 bucket + +## Requirements + +| Name | Version | +|------|---------| +| [aws](#requirement\_aws) | 5.84.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.84.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_s3_bucket.logging](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_acl.this](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_lifecycle_configuration.logging](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_logging.logging](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_ownership_controls.logging](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_ownership_controls) | resource | +| [aws_s3_bucket_ownership_controls.this](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_ownership_controls) | resource | +| [aws_s3_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_policy) | resource | +| [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.logging](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_versioning.this](https://registry.terraform.io/providers/hashicorp/aws/5.84.0/docs/resources/s3_bucket_versioning) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allowed\_principals](#input\_allowed\_principals) | List of IAM principals allowed to access the S3 bucket. Use '*' for public access. | `list(string)` |
[
"*"
]
| no | +| [block\_public\_acls](#input\_block\_public\_acls) | Whether to block public ACLs on the S3 bucket. | `bool` | `true` | no | +| [block\_public\_policy](#input\_block\_public\_policy) | Whether to block public bucket policies. | `bool` | `true` | no | +| [bucket\_acl](#input\_bucket\_acl) | The ACL for the S3 bucket | `string` | `"private"` | no | +| [bucket\_name](#input\_bucket\_name) | The name of the S3 bucket (must be unique, 3-63 characters, lowercase, and DNS-compliant) | `string` | n/a | yes | +| [bucket\_suffix](#input\_bucket\_suffix) | Optional suffix for the S3 bucket name. | `string` | `""` | no | +| [enable\_versioning](#input\_enable\_versioning) | Enable versioning for the bucket | `bool` | `true` | no | +| [force\_destroy](#input\_force\_destroy) | Whether to allow deletion of non-empty bucket | `bool` | `false` | no | +| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Whether to ignore public ACLs for this bucket. | `bool` | `true` | no | +| [logging\_enabled](#input\_logging\_enabled) | Enable logging for the S3 bucket | `bool` | `false` | no | +| [logging\_encryption\_algorithm](#input\_logging\_encryption\_algorithm) | The encryption algorithm used for S3 logging. Valid values: 'AES256', 'aws:kms'. | `string` | `"AES256"` | no | +| [logging\_encryption\_enabled](#input\_logging\_encryption\_enabled) | Enable encryption for S3 logging. | `bool` | `true` | no | +| [logging\_log\_retention\_days](#input\_logging\_log\_retention\_days) | Number of days to retain S3 logging data before expiration. | `number` | `30` | no | +| [logging\_s3\_prefix](#input\_logging\_s3\_prefix) | Prefix for S3 logging objects. | `string` | `"s3/"` | no | +| [object\_ownership](#input\_object\_ownership) | Defines who owns newly uploaded objects in the bucket. | `string` | `"BucketOwnerPreferred"` | no | +| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Whether to restrict public access to the bucket. | `bool` | `true` | no | +| [sse\_algorithm](#input\_sse\_algorithm) | The encryption algorithm for S3 bucket | `string` | `"AES256"` | no | +| [tags](#input\_tags) | Tags for the S3 bucket | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [bucket\_arn](#output\_bucket\_arn) | The ARN of the S3 bucket | +| [bucket\_domain\_name](#output\_bucket\_domain\_name) | The bucket domain name | +| [bucket\_hosted\_zone\_id](#output\_bucket\_hosted\_zone\_id) | The Route 53 hosted zone ID for this bucket | +| [bucket\_id](#output\_bucket\_id) | The ID (name) of the S3 bucket | +| [bucket\_logging\_target](#output\_bucket\_logging\_target) | The target bucket for logging (if logging is enabled) | +| [bucket\_region](#output\_bucket\_region) | The AWS region where the S3 bucket is located | diff --git a/examples/complete/.terraform.lock.hcl b/examples/complete/.terraform.lock.hcl new file mode 100644 index 0000000..ec56e95 --- /dev/null +++ b/examples/complete/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.84.0" + constraints = "5.84.0" + hashes = [ + "h1:dwpeFUdcxgXVAc0JSqO57xf0/r2qOBLPloombCQWFz8=", + "zh:078f77438aba6ec8bf9154b7d223e5c71c48d805d6cd3bcf9db0cc1e82668ac3", + "zh:1f6591ff96be00501e71b792ed3a5a14b21ff03afec9a1c4a3fd9300e6e5b674", + "zh:2ab694e022e81dd74485351c5836148a842ed71cf640664c9d871cb517b09602", + "zh:33c8ccb6e3dc496e828a7572dd981366c6271075c1189f249b9b5236361d7eff", + "zh:6f31068ebad1d627e421c72ccdaafe678c53600ca73714e977bf45ff43ae5d17", + "zh:7488623dccfb639347cae66f9001d39cf06b92e8081975235a1ac3a0ac3f44aa", + "zh:7f042b78b9690a8725c95b91a70fc8e264011b836605bcc342ac297b9ea3937d", + "zh:88b56ac6c7209dc0a775b79975a371918f3aed8f015c37d5899f31deff37c61a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a1979ba840d704af0932f8de5f541cbb4caa9b6bbd25ed552a24e6772175ba07", + "zh:b058c0533dae580e69d1adbc1f69e6a80632374abfc10e8634d06187a108e87b", + "zh:c88610af9cf957f8dcf4382e0c9ca566ef10e3290f5de01d4d90b2d81b078aa8", + "zh:e9562c055a2247d0c287772b55abef468c79f8d66a74780fe1c5e5dae1a284a9", + "zh:f7a7c71d28441d925a25c08c4485c015b2d9f0338bc9707443e91ff8e161d3d9", + "zh:fee533e81976d0900aa6fa443dc54ef171cbd901847f28a6e8edb1d161fa6fde", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.3" + hashes = [ + "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", + ] +} diff --git a/examples/complete/README.md b/examples/complete/README.md new file mode 100644 index 0000000..b02e47f --- /dev/null +++ b/examples/complete/README.md @@ -0,0 +1,149 @@ +# Terraform S3 Bucket Module + +This Terraform module creates and manages an **AWS S3 bucket** with **versioning, encryption, IAM access control, and optional logging**. + +## Features + +- ✅ **Creates an S3 bucket** with a random suffix for uniqueness +- ✅ **Enables versioning** to protect object history +- ✅ **Enforces server-side encryption (`AES256`)** for data security +- ✅ **Configures ownership and access control policies** +- ✅ **Supports IAM principals for restricted access** +- ✅ **Optionally enables logging to a separate bucket** + +--- + +## Usage Example + +```hcl +# Fetch AWS Account ID dynamically +data "aws_caller_identity" "current" {} + +# Generate a random suffix for uniqueness +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +module "s3_bucket" { + source = "../.." + + # General Configuration + bucket_name = "test-bucket" + bucket_suffix = random_string.suffix.result + force_destroy = true + tags = { + Environment = "dev" + Project = "example-project" + } + + # Ownership and Access Controls + object_ownership = "BucketOwnerPreferred" + bucket_acl = "private" + block_public_acls = true + block_public_policy = false + ignore_public_acls = true + restrict_public_buckets = true + allowed_principals = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + + # Security & Encryption + sse_algorithm = "AES256" + + # Versioning + enable_versioning = true + + # Logging Configuration + logging_enabled = true + logging_encryption_enabled = true + logging_encryption_algorithm = "AES256" + logging_log_retention_days = 90 + logging_s3_prefix = "logs/" +} + +# Outputs +output "all_module_outputs" { + description = "All outputs from the S3 Bucket module" + value = module.s3_bucket +} +``` + +--- + +## Inputs + +| Name | Type | Default | Description | +|------------------------------|---------|---------|-------------| +| `bucket_name` | `string` | **Required** | Name of the S3 bucket | +| `bucket_suffix` | `string` | Random | Unique suffix for bucket name | +| `force_destroy` | `bool` | `true` | Force delete non-empty bucket | +| `tags` | `map` | `{}` | Tags for the bucket | +| `object_ownership` | `string` | `"BucketOwnerPreferred"` | Ownership setting | +| `bucket_acl` | `string` | `"private"` | ACL setting | +| `block_public_acls` | `bool` | `true` | Block public ACLs | +| `block_public_policy` | `bool` | `false` | Block public policy | +| `ignore_public_acls` | `bool` | `true` | Ignore public ACLs | +| `restrict_public_buckets` | `bool` | `true` | Restrict public buckets | +| `allowed_principals` | `list(string)` | `["arn:aws:iam::ACCOUNT_ID:root"]` | IAM principals with access | +| `sse_algorithm` | `string` | `"AES256"` | Encryption algorithm | +| `enable_versioning` | `bool` | `true` | Enable versioning | +| `logging_enabled` | `bool` | `true` | Enable logging | +| `logging_encryption_enabled` | `bool` | `true` | Encrypt log bucket | +| `logging_encryption_algorithm` | `string` | `"AES256"` | Encryption for logs | +| `logging_log_retention_days` | `number` | `90` | Log retention period | +| `logging_s3_prefix` | `string` | `"logs/"` | Prefix for logs | + +--- + +## Outputs + +| Name | Description | +|--------------------|-------------| +| `bucket_id` | The name of the S3 bucket | +| `bucket_arn` | The ARN of the S3 bucket | +| `bucket_region` | The AWS region of the bucket | +| `bucket_domain_name` | The domain name of the bucket | +| `bucket_hosted_zone_id` | The Route 53 hosted zone ID for the bucket | +| `bucket_logging_target` | The target logging bucket (if enabled) | + +--- + +## Deployment + +### Initialize Terraform + +```sh +terraform init +``` + +### Apply Configuration + +```sh +terraform apply -auto-approve +``` + +### View Outputs + +```sh +terraform output +``` + +### Destroy Resources + +```sh +terraform destroy -auto-approve +``` + +--- + +## Notes + +- **Logging is optional** and can be disabled by setting `logging_enabled = false`. +- **IAM principals can be customized** based on access requirements. +- **Bucket name must be globally unique**, so the suffix ensures no conflicts. + +--- + +## License + +This module is released under the **MIT License**. diff --git a/examples/complete/main.tf b/examples/complete/main.tf new file mode 100644 index 0000000..91c9859 --- /dev/null +++ b/examples/complete/main.tf @@ -0,0 +1,49 @@ +# Fetch AWS Account ID dynamically +data "aws_caller_identity" "current" {} + +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +module "s3_bucket" { + source = "../.." + + # General Configuration + bucket_name = "test-bucket" + bucket_suffix = random_string.suffix.result + force_destroy = true + tags = { + Environment = "dev" + Project = "example-project" + } + + # Ownership and Access Controls + object_ownership = "BucketOwnerPreferred" + bucket_acl = "private" + block_public_acls = true + block_public_policy = false + ignore_public_acls = true + restrict_public_buckets = true + allowed_principals = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + + # Security & Encryption + sse_algorithm = "AES256" + + # Versioning + enable_versioning = true + + # Logging Configuration + logging_enabled = true + logging_encryption_enabled = true + logging_encryption_algorithm = "AES256" + logging_log_retention_days = 90 + logging_s3_prefix = "logs/" +} + +# Outputs +output "all_module_outputs" { + description = "All outputs from the S3 Bucket module" + value = module.s3_bucket +} diff --git a/examples/minimal/.terraform.lock.hcl b/examples/minimal/.terraform.lock.hcl new file mode 100644 index 0000000..ec56e95 --- /dev/null +++ b/examples/minimal/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.84.0" + constraints = "5.84.0" + hashes = [ + "h1:dwpeFUdcxgXVAc0JSqO57xf0/r2qOBLPloombCQWFz8=", + "zh:078f77438aba6ec8bf9154b7d223e5c71c48d805d6cd3bcf9db0cc1e82668ac3", + "zh:1f6591ff96be00501e71b792ed3a5a14b21ff03afec9a1c4a3fd9300e6e5b674", + "zh:2ab694e022e81dd74485351c5836148a842ed71cf640664c9d871cb517b09602", + "zh:33c8ccb6e3dc496e828a7572dd981366c6271075c1189f249b9b5236361d7eff", + "zh:6f31068ebad1d627e421c72ccdaafe678c53600ca73714e977bf45ff43ae5d17", + "zh:7488623dccfb639347cae66f9001d39cf06b92e8081975235a1ac3a0ac3f44aa", + "zh:7f042b78b9690a8725c95b91a70fc8e264011b836605bcc342ac297b9ea3937d", + "zh:88b56ac6c7209dc0a775b79975a371918f3aed8f015c37d5899f31deff37c61a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a1979ba840d704af0932f8de5f541cbb4caa9b6bbd25ed552a24e6772175ba07", + "zh:b058c0533dae580e69d1adbc1f69e6a80632374abfc10e8634d06187a108e87b", + "zh:c88610af9cf957f8dcf4382e0c9ca566ef10e3290f5de01d4d90b2d81b078aa8", + "zh:e9562c055a2247d0c287772b55abef468c79f8d66a74780fe1c5e5dae1a284a9", + "zh:f7a7c71d28441d925a25c08c4485c015b2d9f0338bc9707443e91ff8e161d3d9", + "zh:fee533e81976d0900aa6fa443dc54ef171cbd901847f28a6e8edb1d161fa6fde", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.3" + hashes = [ + "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", + ] +} diff --git a/examples/minimal/README.md b/examples/minimal/README.md new file mode 100644 index 0000000..8e320f1 --- /dev/null +++ b/examples/minimal/README.md @@ -0,0 +1,104 @@ +# Terraform S3 Bucket Module + +This Terraform module provisions an **AWS S3 bucket** with optional configuration settings for **naming, tagging, and unique suffix generation**. + +## Features + +- ✅ **Creates an S3 bucket** with a unique random suffix +- ✅ **Supports custom bucket naming** +- ✅ **Allows custom tagging** for environment and project identification + +--- + +## Usage Example + +```hcl +# Fetch AWS Account ID dynamically +data "aws_caller_identity" "current" {} + +# Generate a random suffix for uniqueness +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +module "s3_bucket" { + source = "../.." + + # General Configuration + bucket_name = "test-bucket" + bucket_suffix = random_string.suffix.result + tags = { + Environment = "dev" + Project = "example-project" + } +} + +# Outputs +output "all_module_outputs" { + description = "All outputs from the S3 Bucket module" + value = module.s3_bucket +} +``` + +--- + +## Inputs + +| Name | Type | Default | Description | +|-----------------|---------|---------|-------------| +| `bucket_name` | `string` | **Required** | Name of the S3 bucket | +| `bucket_suffix` | `string` | Random | Unique suffix for bucket name | +| `tags` | `map` | `{}` | Tags for the bucket | + +--- + +## Outputs + +| Name | Description | +|--------------------|-------------| +| `bucket_id` | The name of the S3 bucket | +| `bucket_arn` | The ARN of the S3 bucket | +| `bucket_region` | The AWS region of the bucket | + +--- + +## Deployment + +### Initialize Terraform + +```sh +terraform init +``` + +### Apply Configuration + +```sh +terraform apply -auto-approve +``` + +### View Outputs + +```sh +terraform output +``` + +### Destroy Resources + +```sh +terraform destroy -auto-approve +``` + +--- + +## Notes + +- **Bucket name must be globally unique**, so the suffix ensures no conflicts. +- **IAM permissions may be required** to manage the bucket in AWS. + +--- + +## License + +This module is released under the **MIT License**. diff --git a/examples/minimal/main.tf b/examples/minimal/main.tf new file mode 100644 index 0000000..955d6d2 --- /dev/null +++ b/examples/minimal/main.tf @@ -0,0 +1,27 @@ +# Fetch AWS Account ID dynamically +data "aws_caller_identity" "current" {} + +# Generate a random suffix for bucket name +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +module "s3_bucket" { + source = "../.." + + # General Configuration + bucket_name = "test-bucket" + bucket_suffix = random_string.suffix.result + tags = { + Environment = "dev" + Project = "example-project" + } +} + +# Outputs +output "all_module_outputs" { + description = "All outputs from the S3 Bucket module" + value = module.s3_bucket +} diff --git a/examples/standard/.terraform.lock.hcl b/examples/standard/.terraform.lock.hcl new file mode 100644 index 0000000..ec56e95 --- /dev/null +++ b/examples/standard/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.84.0" + constraints = "5.84.0" + hashes = [ + "h1:dwpeFUdcxgXVAc0JSqO57xf0/r2qOBLPloombCQWFz8=", + "zh:078f77438aba6ec8bf9154b7d223e5c71c48d805d6cd3bcf9db0cc1e82668ac3", + "zh:1f6591ff96be00501e71b792ed3a5a14b21ff03afec9a1c4a3fd9300e6e5b674", + "zh:2ab694e022e81dd74485351c5836148a842ed71cf640664c9d871cb517b09602", + "zh:33c8ccb6e3dc496e828a7572dd981366c6271075c1189f249b9b5236361d7eff", + "zh:6f31068ebad1d627e421c72ccdaafe678c53600ca73714e977bf45ff43ae5d17", + "zh:7488623dccfb639347cae66f9001d39cf06b92e8081975235a1ac3a0ac3f44aa", + "zh:7f042b78b9690a8725c95b91a70fc8e264011b836605bcc342ac297b9ea3937d", + "zh:88b56ac6c7209dc0a775b79975a371918f3aed8f015c37d5899f31deff37c61a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a1979ba840d704af0932f8de5f541cbb4caa9b6bbd25ed552a24e6772175ba07", + "zh:b058c0533dae580e69d1adbc1f69e6a80632374abfc10e8634d06187a108e87b", + "zh:c88610af9cf957f8dcf4382e0c9ca566ef10e3290f5de01d4d90b2d81b078aa8", + "zh:e9562c055a2247d0c287772b55abef468c79f8d66a74780fe1c5e5dae1a284a9", + "zh:f7a7c71d28441d925a25c08c4485c015b2d9f0338bc9707443e91ff8e161d3d9", + "zh:fee533e81976d0900aa6fa443dc54ef171cbd901847f28a6e8edb1d161fa6fde", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.3" + hashes = [ + "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", + ] +} diff --git a/examples/standard/README.md b/examples/standard/README.md new file mode 100644 index 0000000..dbadb44 --- /dev/null +++ b/examples/standard/README.md @@ -0,0 +1,133 @@ +# Terraform S3 Bucket Module + +This Terraform module provisions an **AWS S3 bucket** with **versioning, encryption, and access control**. + +## Features + +- ✅ **Creates an S3 bucket** with a unique random suffix +- ✅ **Enables versioning** for object history +- ✅ **Enforces server-side encryption (`AES256`)** +- ✅ **Configures ownership and access control policies** +- ✅ **Supports IAM principals for restricted access** + +--- + +## Usage Example + +```hcl +# Fetch AWS Account ID dynamically +data "aws_caller_identity" "current" {} + +# Generate a random suffix for uniqueness +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +module "s3_bucket" { + source = "../.." + + # General Configuration + bucket_name = "test-bucket" + bucket_suffix = random_string.suffix.result + force_destroy = true + tags = { + Environment = "dev" + Project = "example-project" + } + + # Ownership and Access Controls + object_ownership = "BucketOwnerPreferred" + bucket_acl = "private" + block_public_acls = true + block_public_policy = false + ignore_public_acls = true + restrict_public_buckets = true + allowed_principals = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + + # Security & Encryption + sse_algorithm = "AES256" + + # Versioning + enable_versioning = true +} + +# Outputs +output "all_module_outputs" { + description = "All outputs from the S3 Bucket module" + value = module.s3_bucket +} +``` + +--- + +## Inputs + +| Name | Type | Default | Description | +|------------------------------|---------|---------|-------------| +| `bucket_name` | `string` | **Required** | Name of the S3 bucket | +| `bucket_suffix` | `string` | Random | Unique suffix for bucket name | +| `force_destroy` | `bool` | `true` | Force delete non-empty bucket | +| `tags` | `map` | `{}` | Tags for the bucket | +| `object_ownership` | `string` | `"BucketOwnerPreferred"` | Ownership setting | +| `bucket_acl` | `string` | `"private"` | ACL setting | +| `block_public_acls` | `bool` | `true` | Block public ACLs | +| `block_public_policy` | `bool` | `false` | Block public policy | +| `ignore_public_acls` | `bool` | `true` | Ignore public ACLs | +| `restrict_public_buckets` | `bool` | `true` | Restrict public buckets | +| `allowed_principals` | `list(string)` | `["arn:aws:iam::ACCOUNT_ID:root"]` | IAM principals with access | +| `sse_algorithm` | `string` | `"AES256"` | Encryption algorithm | +| `enable_versioning` | `bool` | `true` | Enable versioning | + +--- + +## Outputs + +| Name | Description | +|--------------------|-------------| +| `bucket_id` | The name of the S3 bucket | +| `bucket_arn` | The ARN of the S3 bucket | +| `bucket_region` | The AWS region of the bucket | +| `bucket_domain_name` | The domain name of the bucket | + +--- + +## Deployment + +### Initialize Terraform + +```sh +terraform init +``` + +### Apply Configuration + +```sh +terraform apply -auto-approve +``` + +### View Outputs + +```sh +terraform output +``` + +### Destroy Resources + +```sh +terraform destroy -auto-approve +``` + +--- + +## Notes + +- **IAM principals can be customized** based on access requirements. +- **Bucket name must be globally unique**, so the suffix ensures no conflicts. + +--- + +## License + +This module is released under the **MIT License**. diff --git a/examples/standard/main.tf b/examples/standard/main.tf new file mode 100644 index 0000000..f526acb --- /dev/null +++ b/examples/standard/main.tf @@ -0,0 +1,43 @@ +# Fetch AWS Account ID dynamically +data "aws_caller_identity" "current" {} + +# Generate a random suffix for bucket name +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +module "s3_bucket" { + source = "../.." + + # General Configuration + bucket_name = "test-bucket" + bucket_suffix = random_string.suffix.result + force_destroy = true + tags = { + Environment = "dev" + Project = "example-project" + } + + # Ownership and Access Controls + object_ownership = "BucketOwnerPreferred" + bucket_acl = "private" + block_public_acls = true + block_public_policy = false + ignore_public_acls = true + restrict_public_buckets = true + allowed_principals = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + + # Security & Encryption + sse_algorithm = "AES256" + + # Versioning + enable_versioning = true +} + +# Outputs +output "all_module_outputs" { + description = "All outputs from the S3 Bucket module" + value = module.s3_bucket +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..125afd2 --- /dev/null +++ b/main.tf @@ -0,0 +1,150 @@ +locals { + base_bucket_name = ( + var.bucket_suffix == "" ? + var.bucket_name : + "${var.bucket_name}-${var.bucket_suffix}") +} + +############################################ +# S3 BUCKET CONFIGURATION +############################################ + +resource "aws_s3_bucket" "this" { + bucket = local.base_bucket_name + force_destroy = var.force_destroy + tags = var.tags +} + +resource "aws_s3_bucket_ownership_controls" "this" { + bucket = aws_s3_bucket.this.id + + rule { + object_ownership = var.object_ownership + } +} + +resource "aws_s3_bucket_public_access_block" "this" { + bucket = aws_s3_bucket.this.id + + block_public_acls = var.block_public_acls + block_public_policy = var.block_public_policy + ignore_public_acls = var.ignore_public_acls + restrict_public_buckets = var.restrict_public_buckets +} + +resource "aws_s3_bucket_acl" "this" { + bucket = aws_s3_bucket.this.id + acl = var.bucket_acl + + depends_on = [ + aws_s3_bucket_ownership_controls.this, + aws_s3_bucket_public_access_block.this, + ] +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "this" { + bucket = aws_s3_bucket.this.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = var.sse_algorithm + } + } +} + +resource "aws_s3_bucket_versioning" "this" { + bucket = aws_s3_bucket.this.id + + versioning_configuration { + status = var.enable_versioning ? "Enabled" : "Disabled" + } +} + +resource "aws_s3_bucket_policy" "this" { + count = var.block_public_policy == false ? 1 : 0 + + bucket = aws_s3_bucket.this.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = "s3:GetObject" + Resource = "${aws_s3_bucket.this.arn}/*" + Principal = { + AWS = join(",", var.allowed_principals) + } + } + ] + }) + + depends_on = [ + aws_s3_bucket_acl.this + ] +} + +############################################ +# S3 LOGGING CONFIGURATION +############################################ + +resource "aws_s3_bucket" "logging" { + count = var.logging_enabled ? 1 : 0 + + bucket = "${local.base_bucket_name}-logs" + force_destroy = var.force_destroy + tags = merge(var.tags, { Name = "${local.base_bucket_name}-logs" }) +} + +resource "aws_s3_bucket_ownership_controls" "logging" { + count = var.logging_enabled ? 1 : 0 + + bucket = aws_s3_bucket.logging[0].id + + rule { + object_ownership = var.object_ownership + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "logging" { + count = var.logging_enabled && var.logging_encryption_enabled ? 1 : 0 + + bucket = aws_s3_bucket.logging[0].id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = var.logging_encryption_algorithm + } + } +} + +resource "aws_s3_bucket_lifecycle_configuration" "logging" { + count = var.logging_enabled ? 1 : 0 + + bucket = aws_s3_bucket.logging[0].id + + rule { + id = "log-retention" + status = "Enabled" + + expiration { + days = var.logging_log_retention_days + } + + noncurrent_version_expiration { + noncurrent_days = var.logging_log_retention_days + } + } +} + +resource "aws_s3_bucket_logging" "logging" { + count = var.logging_enabled ? 1 : 0 + + bucket = aws_s3_bucket.this.id + target_bucket = aws_s3_bucket.logging[0].id + target_prefix = var.logging_s3_prefix + + depends_on = [ + aws_s3_bucket.logging + ] +} diff --git a/output.tf b/output.tf new file mode 100644 index 0000000..eb2dfda --- /dev/null +++ b/output.tf @@ -0,0 +1,29 @@ +output "bucket_id" { + description = "The ID (name) of the S3 bucket" + value = aws_s3_bucket.this.id +} + +output "bucket_arn" { + description = "The ARN of the S3 bucket" + value = aws_s3_bucket.this.arn +} + +output "bucket_region" { + description = "The AWS region where the S3 bucket is located" + value = aws_s3_bucket.this.region +} + +output "bucket_domain_name" { + description = "The bucket domain name" + value = aws_s3_bucket.this.bucket_domain_name +} + +output "bucket_hosted_zone_id" { + description = "The Route 53 hosted zone ID for this bucket" + value = aws_s3_bucket.this.hosted_zone_id +} + +output "bucket_logging_target" { + description = "The target bucket for logging (if logging is enabled)" + value = var.logging_enabled ? aws_s3_bucket.logging[0].id : null +} diff --git a/tests/s3_bucket.tftest.hcl b/tests/s3_bucket.tftest.hcl new file mode 100644 index 0000000..a14ac73 --- /dev/null +++ b/tests/s3_bucket.tftest.hcl @@ -0,0 +1,98 @@ +run "setup" { + module { + source = "./tests/setup" + } +} + +run "test_s3_bucket" { + variables { + bucket_name = "test-s3-bucket" + bucket_suffix = run.setup.suffix + force_destroy = true + tags = { + Environment = "dev" + Project = "example-project" + } + + # Ownership & Access Control + object_ownership = "BucketOwnerPreferred" + bucket_acl = "private" + allowed_principals = ["arn:aws:iam::${run.setup.account_id}:root"] + + # Public Access Configuration + block_public_acls = true + block_public_policy = false + ignore_public_acls = true + restrict_public_buckets = true + + # Security & Encryption + sse_algorithm = "AES256" + + # Versioning + enable_versioning = true + + # Logging Configuration + logging_enabled = true + logging_encryption_enabled = true + logging_encryption_algorithm = "AES256" + logging_log_retention_days = 90 + logging_s3_prefix = "logs/" + } + + # Assertions referencing actual Terraform resources + + assert { + condition = aws_s3_bucket.this.bucket == "test-s3-bucket-${run.setup.suffix}" + error_message = "Bucket name does not match expected value." + } + + assert { + condition = aws_s3_bucket_acl.this.acl == "private" + error_message = "Bucket ACL is not set to 'private'." + } + + assert { + condition = aws_s3_bucket_ownership_controls.this.rule[0].object_ownership == "BucketOwnerPreferred" + error_message = "Object ownership is not set to 'BucketOwnerPreferred'." + } + + assert { + condition = aws_s3_bucket_versioning.this.versioning_configuration[0].status == "Enabled" + error_message = "Versioning is not enabled." + } + + assert { + condition = aws_s3_bucket_public_access_block.this.block_public_policy == false + error_message = "Block public policy should be set to 'false'." + } + + assert { + condition = aws_s3_bucket_public_access_block.this.block_public_acls == true + error_message = "Block public ACLs should be set to 'true'." + } + + assert { + condition = aws_s3_bucket_public_access_block.this.ignore_public_acls == true + error_message = "Ignore public ACLs should be set to 'true'." + } + + assert { + condition = aws_s3_bucket_public_access_block.this.restrict_public_buckets == true + error_message = "Restrict public buckets should be set to 'true'." + } + + assert { + condition = flatten([for rule in aws_s3_bucket_server_side_encryption_configuration.this.rule : rule.apply_server_side_encryption_by_default[*].sse_algorithm]) == ["AES256"] + error_message = "SSE algorithm is not set to 'AES256'." + } + + assert { + condition = aws_s3_bucket_logging.logging[0].target_bucket == "${aws_s3_bucket.this.id}-logs" + error_message = "Logging target bucket does not match expected value." + } + + assert { + condition = aws_s3_bucket_logging.logging[0].target_prefix == "logs/" + error_message = "Logging S3 prefix is not set to 'logs/'." + } +} diff --git a/tests/setup/main.tf b/tests/setup/main.tf new file mode 100644 index 0000000..d1012ee --- /dev/null +++ b/tests/setup/main.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.0" +} + +# Fetch AWS Account ID dynamically +data "aws_caller_identity" "current" {} + +# Generate a random string as suffix +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +# Output suffix for use in tests +output "suffix" { + value = random_string.suffix.result +} + +output "account_id" { + value = data.aws_caller_identity.current.account_id +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..bbe2771 --- /dev/null +++ b/variables.tf @@ -0,0 +1,167 @@ +############################################ +# GENERAL BUCKET CONFIGURATION VARIABLES +############################################ + +variable "bucket_name" { + description = "The name of the S3 bucket (must be unique, 3-63 characters, lowercase, and DNS-compliant)" + type = string + + validation { + condition = length(var.bucket_name) >= 3 && length(var.bucket_name) <= 63 && can(regex("^[a-z0-9][a-z0-9.-]*[a-z0-9]$", var.bucket_name)) + error_message = "Bucket name must be 3-63 characters long, lowercase, contain only letters, numbers, dots (.), and hyphens (-), and must not start or end with a dot or hyphen." + } +} + +variable "bucket_suffix" { + description = "Optional suffix for the S3 bucket name." + type = string + default = "" +} + +variable "force_destroy" { + description = "Whether to allow deletion of non-empty bucket" + type = bool + default = false +} + +variable "bucket_acl" { + description = "The ACL for the S3 bucket" + type = string + default = "private" + + validation { + condition = contains(["private", "public-read", "public-read-write", "authenticated-read", "aws-exec-read", "bucket-owner-read", "bucket-owner-full-control"], var.bucket_acl) + error_message = "Allowed values for bucket_acl are 'private', 'public-read', 'public-read-write', 'authenticated-read', 'aws-exec-read', 'bucket-owner-read', or 'bucket-owner-full-control'." + } +} + +variable "enable_versioning" { + description = "Enable versioning for the bucket" + type = bool + default = true +} + +variable "sse_algorithm" { + description = "The encryption algorithm for S3 bucket" + type = string + default = "AES256" + + validation { + condition = contains(["AES256", "aws:kms"], var.sse_algorithm) + error_message = "Allowed values for sse_algorithm are 'AES256' or 'aws:kms'." + } +} + +variable "object_ownership" { + description = "Defines who owns newly uploaded objects in the bucket." + type = string + default = "BucketOwnerPreferred" + + validation { + condition = contains(["BucketOwnerPreferred", "ObjectWriter", "BucketOwnerEnforced"], var.object_ownership) + error_message = "The object ownership setting must be one of 'BucketOwnerPreferred', 'ObjectWriter', or 'BucketOwnerEnforced'." + } +} + +############################################ +# PUBLIC ACCESS & SECURITY SETTINGS +############################################ + +variable "block_public_acls" { + description = "Whether to block public ACLs on the S3 bucket." + type = bool + default = true +} + +variable "block_public_policy" { + description = "Whether to block public bucket policies." + type = bool + default = true +} + +variable "ignore_public_acls" { + description = "Whether to ignore public ACLs for this bucket." + type = bool + default = true +} + +variable "restrict_public_buckets" { + description = "Whether to restrict public access to the bucket." + type = bool + default = true +} + +variable "allowed_principals" { + description = "List of IAM principals allowed to access the S3 bucket. Use '*' for public access." + type = list(string) + default = ["*"] + + validation { + condition = length(var.allowed_principals) > 0 + error_message = "The allowed_principals list cannot be empty." + } +} + +############################################ +# LOGGING CONFIGURATION VARIABLES +############################################ + +variable "logging_enabled" { + description = "Enable logging for the S3 bucket" + type = bool + default = false +} + +variable "logging_encryption_enabled" { + description = "Enable encryption for S3 logging." + type = bool + default = true +} + +variable "logging_encryption_algorithm" { + description = "The encryption algorithm used for S3 logging. Valid values: 'AES256', 'aws:kms'." + type = string + default = "AES256" + + validation { + condition = contains(["AES256", "aws:kms"], var.logging_encryption_algorithm) + error_message = "Allowed values for logging_encryption_algorithm are 'AES256' or 'aws:kms'." + } +} + +variable "logging_log_retention_days" { + description = "Number of days to retain S3 logging data before expiration." + type = number + default = 30 + + validation { + condition = var.logging_log_retention_days > 0 + error_message = "The retention period must be greater than 0 days." + } +} + +variable "logging_s3_prefix" { + description = "Prefix for S3 logging objects." + type = string + default = "s3/" + + validation { + condition = can(regex("^[a-zA-Z0-9!_.*'()/~-]+$", var.logging_s3_prefix)) + error_message = "The logging S3 prefix must be a valid S3 object key prefix, containing only alphanumeric characters and special characters like ! _ . * ' ( ) / ~ -." + } +} + +############################################ +# TAGS CONFIGURATION +############################################ + +variable "tags" { + description = "Tags for the S3 bucket" + type = map(string) + default = {} + + validation { + condition = alltrue([for k, v in var.tags : length(k) > 0 && length(v) > 0]) + error_message = "All tag keys and values must be non-empty strings." + } +} diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..ad82eda --- /dev/null +++ b/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.84.0" + } + } +} + +# provider "aws" { +# region = var.region +# }