diff --git a/.forgejo/workflows/main.yaml b/.forgejo/workflows/main.yaml new file mode 100644 index 0000000..2e4bd1f --- /dev/null +++ b/.forgejo/workflows/main.yaml @@ -0,0 +1,16 @@ +--- +name: 'main' + +on: + push: + workflow_dispatch: + +jobs: + test: + runs-on: 'self-hosted' + steps: + - uses: 'actions/checkout@v4' + - uses: "https://git.adyxax.org/adyxax/action-tofu-aws-test@1.0.0" + with: + aws-access-key-id: "${{ vars.AWS_ACCESS_KEY_ID }}" + aws-access-key-secret: "${{ secrets.AWS_ACCESS_KEY_SECRET }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index d511684..f1b43c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,14 @@ All notable changes to this project will be documented in this file. +## 1.1.0 - 2025-04-12 + +### Added + +- Added default value `[]` to input variable `assume_role_account_names`. + ## 1.0.0 - 2025-04-11 ### Added -- initial import +- Initial import. diff --git a/infrastructure/tofu/.gitignore b/infrastructure/tofu/.gitignore new file mode 100644 index 0000000..a8c8222 --- /dev/null +++ b/infrastructure/tofu/.gitignore @@ -0,0 +1 @@ +!.terraform.lock.hcl diff --git a/infrastructure/tofu/.terraform.lock.hcl b/infrastructure/tofu/.terraform.lock.hcl new file mode 100644 index 0000000..b85e74f --- /dev/null +++ b/infrastructure/tofu/.terraform.lock.hcl @@ -0,0 +1,18 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/adyxax/forgejo" { + version = "1.1.0" + constraints = "1.1.0" + hashes = [ + "h1:xa2K1rn2OzQofizev01UBKEgq4WHo3EM5/fiPCxFL/E=", + ] +} + +provider "registry.opentofu.org/hashicorp/aws" { + version = "6.2.0" + constraints = "6.2.0" + hashes = [ + "h1:UcBl0SyNxOTHOa3Ske3ClmzA7V1S7e/I4+29DLGe85A=", + ] +} diff --git a/infrastructure/tofu/main.tf b/infrastructure/tofu/main.tf new file mode 100644 index 0000000..682f220 --- /dev/null +++ b/infrastructure/tofu/main.tf @@ -0,0 +1,34 @@ +locals { + name = "tofu-module-aws-iam-user" +} + +module "aws_iam_ci_user" { + providers = { + aws.core = aws.all["core"] + aws.root = aws.all["root"] + aws.tests = aws.all["tests"] + } + source = "git::ssh://git@git.adyxax.org/adyxax/tofu-module-aws-iam-ci-user?depth=1&ref=1.1.0" + + forgejo_repository = { + name = local.name + owner = "adyxax" + } + name = local.name + tests_policy_statements = jsonencode([ + { + Action = "iam:*" + Effect = "Allow" + Resource = [ + "arn:aws:iam::*:user/tftest-user", + "arn:aws:iam::*:policy/${local.name}-tftest", + ] + }, + { + # Necessary for removing an IAM user + Action = "iam:ListVirtualMFADevices", + Effect = "Allow" + Resource = "*" + } + ]) +} diff --git a/infrastructure/tofu/providers.tf b/infrastructure/tofu/providers.tf new file mode 100644 index 0000000..a312874 --- /dev/null +++ b/infrastructure/tofu/providers.tf @@ -0,0 +1,32 @@ +terraform { + backend "s3" { + bucket = "adyxax-tofu-states" + dynamodb_table = "tofu-states" + key = "repositories/${local.name}" + profile = "core" + region = "eu-west-3" + } + required_providers { + aws = { + source = "hashicorp/aws" + version = "6.2.0" + } + forgejo = { + source = "adyxax/forgejo" + version = "1.1.0" + } + } +} + +provider "aws" { + for_each = toset(["core", "root", "tests"]) + + alias = "all" + default_tags { tags = { "managed-by" = "tofu" } } + profile = each.key + region = "eu-west-3" +} + +provider "forgejo" { + base_uri = "https://git.adyxax.org/" +} diff --git a/main.tf b/main.tf index f46bfb4..0c0989a 100644 --- a/main.tf +++ b/main.tf @@ -1,9 +1,10 @@ data "aws_organizations_organization" "main" {} locals { - aws_account_ids = { for info in data.aws_organizations_organization.main.accounts : + aws_account_ids = length(var.assume_role_account_names) > 0 ? { + for info in data.aws_organizations_organization.main.accounts : info.name => info.id - } + } : {} } resource "aws_iam_user" "main" { @@ -14,43 +15,47 @@ resource "aws_iam_user" "main" { resource "aws_iam_user_policy" "main" { name = var.name policy = jsonencode({ - Statement = concat([ - { # Assume roles in AWS sub-accounts - Action = "sts:AssumeRole" - Effect = "Allow" - Resource = [for name in var.assume_role_account_names : - format( - "arn:aws:iam::%s:role/%s", - local.aws_account_ids[name], - var.name, - ) - ] - }, - { - Action = [ - # Manage the user's own IAM access key - "iam:CreateAccessKey", - "iam:DeleteAccessKey", - "iam:UpdateAccessKey", - # Read only access to the user's IAM object - "iam:Get*", - "iam:List*", - ] - Effect = "Allow" - Resource = aws_iam_user.main.arn - }, - { - Action = [ - # Necessary for removing an IAM user - "iam:ListVirtualMFADevices", - # Describe and list the organization accounts - "organizations:DescribeOrganization", - "organizations:List*", - ] - Effect = "Allow" - Resource = "*" - }, - ]) + Statement = concat( + length(var.assume_role_account_names) > 0 ? [ + { # Assume roles in AWS sub-accounts + Action = "sts:AssumeRole" + Effect = "Allow" + Resource = [for name in var.assume_role_account_names : + format( + "arn:aws:iam::%s:role/%s", + local.aws_account_ids[name], + var.name, + ) + ] + } + ] : [], + [ + { + Action = [ + # Manage the user's own IAM access key + "iam:CreateAccessKey", + "iam:DeleteAccessKey", + "iam:UpdateAccessKey", + # Read only access to the user's IAM object + "iam:Get*", + "iam:List*", + ] + Effect = "Allow" + Resource = aws_iam_user.main.arn + }, + { + Action = [ + # Necessary for removing an IAM user + "iam:ListVirtualMFADevices", + # Describe and list the organization accounts + "organizations:DescribeOrganization", + "organizations:List*", + ] + Effect = "Allow" + Resource = "*" + }, + ] + ) Version = "2012-10-17" }) user = aws_iam_user.main.name diff --git a/main.tftest.hcl b/main.tftest.hcl new file mode 100644 index 0000000..90ccb91 --- /dev/null +++ b/main.tftest.hcl @@ -0,0 +1,14 @@ +provider "aws" { + profile = "tests" + region = "eu-west-3" +} + +run "main" { + assert { + condition = data.external.main.result.Arn == local.expected_arn + error_message = "user ARN mismatch" + } + module { + source = "./test" + } +} diff --git a/test/aws_config.tftpl b/test/aws_config.tftpl new file mode 100644 index 0000000..a5470b2 --- /dev/null +++ b/test/aws_config.tftpl @@ -0,0 +1,4 @@ +[default] +aws_access_key_id = ${aws_access_key_id} +aws_secret_access_key = ${aws_access_key_secret} +region = eu-west-3 diff --git a/test/main.tf b/test/main.tf new file mode 100644 index 0000000..450636a --- /dev/null +++ b/test/main.tf @@ -0,0 +1,31 @@ +module "main" { + source = "../" + + name = "tftest-user" +} + +data "aws_caller_identity" "current" {} + +# tflint-ignore: terraform_unused_declarations +data "external" "main" { + program = ["${path.module}/test.sh"] + + depends_on = [local_file.aws_config] +} + +locals { + # tflint-ignore: terraform_unused_declarations + expected_arn = format( + "arn:aws:iam::%s:user/tftest-user", + data.aws_caller_identity.current.account_id, + ) +} + +resource "local_file" "aws_config" { + filename = "${path.module}/aws_config" + file_permission = "0600" + content = templatefile("${path.module}/aws_config.tftpl", { + aws_access_key_id = module.main.access_key_id + aws_access_key_secret = module.main.access_key_secret + }) +} diff --git a/test/providers.tf b/test/providers.tf new file mode 100644 index 0000000..7886647 --- /dev/null +++ b/test/providers.tf @@ -0,0 +1,15 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + } + external = { + source = "hashicorp/external" + version = "2.3.4" + } + local = { + source = "hashicorp/local" + version = "2.5.2" + } + } +} diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 0000000..325fcd2 --- /dev/null +++ b/test/test.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Wait a bit for the ACCESS KEY to be usable on AWS +sleep 10 + +export AWS_CONFIG_FILE="${PWD}/test/aws_config" +aws sts get-caller-identity diff --git a/variables.tf b/variables.tf index aed2f86..3c00266 100644 --- a/variables.tf +++ b/variables.tf @@ -1,4 +1,5 @@ variable "assume_role_account_names" { + default = [] description = "The names of the AWS sub-accounts this IAM user can assume roles in." nullable = false type = list(string)