From 51bdcc5fc0c65e262a02e949c74d19831998b61e Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Sat, 12 Apr 2025 08:53:00 +0200 Subject: [PATCH 1/4] feat(module): add default value `[]` to input variable `assume_role_account_names` --- CHANGELOG.md | 8 ++++- main.tf | 83 ++++++++++++++++++++++++++++------------------------ variables.tf | 1 + 3 files changed, 52 insertions(+), 40 deletions(-) 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/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/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) From 959edc9d00a3e201ab5d789573c186a9f84a6bd4 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Sat, 12 Apr 2025 08:55:40 +0200 Subject: [PATCH 2/4] chore(infrastructure): bootstrap CI --- .forgejo/workflows/main.yaml | 16 +++++++++++ infrastructure/tofu/.gitignore | 1 + infrastructure/tofu/.terraform.lock.hcl | 21 ++++++++++++++ infrastructure/tofu/main.tf | 38 +++++++++++++++++++++++++ infrastructure/tofu/providers.tf | 24 ++++++++++++++++ main.tftest.hcl | 15 ++++++++++ 6 files changed, 115 insertions(+) create mode 100644 .forgejo/workflows/main.yaml create mode 100644 infrastructure/tofu/.gitignore create mode 100644 infrastructure/tofu/.terraform.lock.hcl create mode 100644 infrastructure/tofu/main.tf create mode 100644 infrastructure/tofu/providers.tf create mode 100644 main.tftest.hcl 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/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..9ee3c83 --- /dev/null +++ b/infrastructure/tofu/.terraform.lock.hcl @@ -0,0 +1,21 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/aws" { + version = "5.91.0" + constraints = "5.91.0" + hashes = [ + "h1:g+uDHz6bZ36QaxoKWmJEYGh7OP5RAE5MPbxLohzcU18=", + "h1:qw1Sp5py+7rRwzHgHNJvgYTeTkBnPHY7WercO1BsOh0=", + "zh:057e6cb85e3efe2c30ef5ca47cc47abc8217e2e0dddce2e92a8d2d6f18b6cee6", + "zh:0f15d3f599e07307ac9771c602dcaaf0c46dd259649da985cb3cb80a7a647cea", + "zh:187086070cc878ab0a27163939516983e3efae65ebff78dc3466619cdf978dee", + "zh:67a58fc85c630bcc6c772f573813caabe6c9af291c71c7207590fc4792e4d94e", + "zh:68abb9382928ce29c0f3dd9a75b41ad2a453f3a46330f484d1ea858589146c1b", + "zh:772134ba713e879e5b70d614d08a6650f156e7a3fa724d538bfa85632b1ed639", + "zh:bf67439e47cf6720dcec7a1e2988d6c10c56d7eea69bb1ecff1b22d6bb63a36d", + "zh:bfd0b91dc4ae338eb79ec41bede5eed7a0740380bffbdfbda362f7ed08e0e2ad", + "zh:ca3c3313cd4971850da45ce4337b027a804389db740c310ba637bc0a86775eef", + "zh:d75a8ec54a4783c25cb806b887f0d3c67cded08db8c496fd9cf831791e4c8482", + ] +} diff --git a/infrastructure/tofu/main.tf b/infrastructure/tofu/main.tf new file mode 100644 index 0000000..6180f21 --- /dev/null +++ b/infrastructure/tofu/main.tf @@ -0,0 +1,38 @@ +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.0.1" + + name = local.name +} + +resource "aws_iam_policy" "tftest" { + provider = aws.all["root"] + + name = "${local.name}-tftest" + policy = jsonencode({ + Statement = [{ + Action = "iam:*" + Effect = "Allow" + Resource = [ + "arn:aws:iam::*:user/tftest-user", + "arn:aws:iam::*:policy/${local.name}-tftest", + ] + }] + Version = "2012-10-17" + }) +} + +resource "aws_iam_user_policy_attachment" "tftest" { + provider = aws.all["root"] + + policy_arn = aws_iam_policy.tftest.arn + user = local.name +} diff --git a/infrastructure/tofu/providers.tf b/infrastructure/tofu/providers.tf new file mode 100644 index 0000000..8b42979 --- /dev/null +++ b/infrastructure/tofu/providers.tf @@ -0,0 +1,24 @@ +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 = "5.91.0" + } + } +} + +provider "aws" { + for_each = toset(["core", "root", "tests"]) + + alias = "all" + default_tags { tags = { "managed-by" = "tofu" } } + profile = each.key + region = "eu-west-3" +} diff --git a/main.tftest.hcl b/main.tftest.hcl new file mode 100644 index 0000000..1662cf6 --- /dev/null +++ b/main.tftest.hcl @@ -0,0 +1,15 @@ +provider "aws" { + profile = "root" + region = "eu-west-3" +} + +run "main" { + assert { + condition = output.access_key_id != null + error_message = "invalid IAM access key ID" + } +} + +variables { + name = "tftest-user" +} From 00ee290f2976dad1855168ebf4e7ff1a0efe314c Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Thu, 17 Apr 2025 16:50:33 +0200 Subject: [PATCH 3/4] chore(infrastructure): change the AWS account used for tests --- infrastructure/tofu/main.tf | 28 ++++++++++------------------ main.tftest.hcl | 2 +- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/infrastructure/tofu/main.tf b/infrastructure/tofu/main.tf index 6180f21..ced2b09 100644 --- a/infrastructure/tofu/main.tf +++ b/infrastructure/tofu/main.tf @@ -11,28 +11,20 @@ module "aws_iam_ci_user" { source = "git::ssh://git@git.adyxax.org/adyxax/tofu-module-aws-iam-ci-user?depth=1&ref=1.0.1" name = local.name -} - -resource "aws_iam_policy" "tftest" { - provider = aws.all["root"] - - name = "${local.name}-tftest" - policy = jsonencode({ - Statement = [{ + tests_policy_statements = jsonencode([ + { Action = "iam:*" Effect = "Allow" Resource = [ "arn:aws:iam::*:user/tftest-user", "arn:aws:iam::*:policy/${local.name}-tftest", ] - }] - Version = "2012-10-17" - }) -} - -resource "aws_iam_user_policy_attachment" "tftest" { - provider = aws.all["root"] - - policy_arn = aws_iam_policy.tftest.arn - user = local.name + }, + { + # Necessary for removing an IAM user + Action = "iam:ListVirtualMFADevices", + Effect = "Allow" + Resource = "*" + } + ]) } diff --git a/main.tftest.hcl b/main.tftest.hcl index 1662cf6..f5e0950 100644 --- a/main.tftest.hcl +++ b/main.tftest.hcl @@ -1,5 +1,5 @@ provider "aws" { - profile = "root" + profile = "tests" region = "eu-west-3" } From c7927f49ebcd571e84c2119af3c9e55e89ed966d Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Sat, 26 Apr 2025 00:31:19 +0200 Subject: [PATCH 4/4] test(module): really test the generated access key --- main.tftest.hcl | 11 +++++------ test/aws_config.tftpl | 4 ++++ test/main.tf | 31 +++++++++++++++++++++++++++++++ test/providers.tf | 15 +++++++++++++++ test/test.sh | 8 ++++++++ 5 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 test/aws_config.tftpl create mode 100644 test/main.tf create mode 100644 test/providers.tf create mode 100755 test/test.sh diff --git a/main.tftest.hcl b/main.tftest.hcl index f5e0950..90ccb91 100644 --- a/main.tftest.hcl +++ b/main.tftest.hcl @@ -5,11 +5,10 @@ provider "aws" { run "main" { assert { - condition = output.access_key_id != null - error_message = "invalid IAM access key ID" + condition = data.external.main.result.Arn == local.expected_arn + error_message = "user ARN mismatch" + } + module { + source = "./test" } } - -variables { - name = "tftest-user" -} 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