From 1b2f055361cd4d4e3b20438d138fa93c9e45f7c1 Mon Sep 17 00:00:00 2001 From: Kamil Rosenberger Date: Fri, 24 Feb 2023 11:53:36 +0100 Subject: [PATCH] Terraform Modules migration: 0f411b0 --- .gitignore | 50 ++++++ aws_iam_identity_provider/README.md | 58 +++++++ aws_iam_identity_provider/locals.tf | 50 ++++++ aws_iam_identity_provider/main.tf | 50 ++++++ aws_iam_identity_provider/outputs.tf | 4 + aws_iam_identity_provider/variables.tf | 34 ++++ aws_identity_providers/README.md | 0 .../modules/bitbucket_oidc/README.md | 24 +++ .../json/bitbucket_oidc_policy.json | 19 +++ .../modules/bitbucket_oidc/locals.tf | 9 ++ .../modules/bitbucket_oidc/main.tf | 43 +++++ .../modules/bitbucket_oidc/variables.tf | 35 ++++ .../modules/bitbucket_oidc/versions.tf | 10 ++ aws_organization/README.md | 1 + aws_organization/locals.tf | 36 +++++ aws_organization/main.tf | 18 +++ .../modules/aws_account_alias/README.md | 0 .../modules/aws_account_alias/locals.tf | 4 + .../modules/aws_account_alias/main.tf | 3 + .../modules/aws_account_alias/outputs.tf | 3 + .../modules/aws_account_alias/variables.tf | 31 ++++ .../modules/aws_accounts/README.md | 3 + aws_organization/modules/aws_accounts/main.tf | 13 ++ .../modules/aws_accounts/outputs.tf | 3 + .../modules/aws_accounts/variables.tf | 28 ++++ .../modules/aws_accounts/versions.tf | 14 ++ .../aws_get_organization_details/README.md | 41 +++++ .../aws_get_organization_details/data.tf | 32 ++++ .../aws_get_organization_details/locals.tf | 28 ++++ .../aws_get_organization_details/outputs.tf | 41 +++++ .../aws_get_organization_details/versions.tf | 10 ++ .../aws_organizational_units/README.md | 28 ++++ .../aws_organizational_units/locals.tf | 13 ++ .../modules/aws_organizational_units/main.tf | 42 +++++ .../aws_organizational_units/outputs.tf | 11 ++ .../aws_organizational_units/variables.tf | 9 ++ .../aws_organizational_units/versions.tf | 10 ++ aws_organization/outputs.tf | 7 + aws_organization/variables.tf | 19 +++ aws_organization/versions.tf | 10 ++ aws_s3_backend/README.md | 1 + aws_s3_backend/data.tf | 5 + aws_s3_backend/locals.tf | 21 +++ aws_s3_backend/main.tf | 150 ++++++++++++++++++ aws_s3_backend/outputs.tf | 21 +++ .../templates/dynamodb_access_policy.tftpl | 34 ++++ .../templates/iam_role_policy.tftpl | 21 +++ .../templates/s3_bucket_access_policy.tftpl | 27 ++++ .../templates/s3_bucket_policy.tftpl | 47 ++++++ .../state_access_iam_role_policy.tftpl | 24 +++ aws_s3_backend/variables.tf | 14 ++ aws_s3_backend/versions.tf | 11 ++ format_data/locals.tf | 102 ++++++++++++ format_data/outputs.tf | 58 +++++++ format_data/variables.tf | 83 ++++++++++ ses_to_s3_email_forwarder/data.tf | 1 + ses_to_s3_email_forwarder/main.tf | 87 ++++++++++ ses_to_s3_email_forwarder/outputs.tf | 4 + .../templates/s3_bucket_policy.tftpl | 19 +++ ses_to_s3_email_forwarder/variables.tf | 53 +++++++ ses_to_s3_email_forwarder/versions.tf | 11 ++ terragrunt_files/locals.tf | 84 ++++++++++ terragrunt_files/main.tf | 7 + terragrunt_files/templates/env_config.tftpl | 5 + .../templates/module_config.tftpl | 10 ++ terragrunt_files/variables.tf | 103 ++++++++++++ 66 files changed, 1847 insertions(+) create mode 100644 .gitignore create mode 100644 aws_iam_identity_provider/README.md create mode 100644 aws_iam_identity_provider/locals.tf create mode 100644 aws_iam_identity_provider/main.tf create mode 100644 aws_iam_identity_provider/outputs.tf create mode 100644 aws_iam_identity_provider/variables.tf create mode 100644 aws_identity_providers/README.md create mode 100644 aws_identity_providers/modules/bitbucket_oidc/README.md create mode 100644 aws_identity_providers/modules/bitbucket_oidc/json/bitbucket_oidc_policy.json create mode 100644 aws_identity_providers/modules/bitbucket_oidc/locals.tf create mode 100644 aws_identity_providers/modules/bitbucket_oidc/main.tf create mode 100644 aws_identity_providers/modules/bitbucket_oidc/variables.tf create mode 100644 aws_identity_providers/modules/bitbucket_oidc/versions.tf create mode 100644 aws_organization/README.md create mode 100644 aws_organization/locals.tf create mode 100644 aws_organization/main.tf create mode 100644 aws_organization/modules/aws_account_alias/README.md create mode 100644 aws_organization/modules/aws_account_alias/locals.tf create mode 100644 aws_organization/modules/aws_account_alias/main.tf create mode 100644 aws_organization/modules/aws_account_alias/outputs.tf create mode 100644 aws_organization/modules/aws_account_alias/variables.tf create mode 100644 aws_organization/modules/aws_accounts/README.md create mode 100644 aws_organization/modules/aws_accounts/main.tf create mode 100644 aws_organization/modules/aws_accounts/outputs.tf create mode 100644 aws_organization/modules/aws_accounts/variables.tf create mode 100644 aws_organization/modules/aws_accounts/versions.tf create mode 100644 aws_organization/modules/aws_get_organization_details/README.md create mode 100644 aws_organization/modules/aws_get_organization_details/data.tf create mode 100644 aws_organization/modules/aws_get_organization_details/locals.tf create mode 100644 aws_organization/modules/aws_get_organization_details/outputs.tf create mode 100644 aws_organization/modules/aws_get_organization_details/versions.tf create mode 100644 aws_organization/modules/aws_organizational_units/README.md create mode 100644 aws_organization/modules/aws_organizational_units/locals.tf create mode 100644 aws_organization/modules/aws_organizational_units/main.tf create mode 100644 aws_organization/modules/aws_organizational_units/outputs.tf create mode 100644 aws_organization/modules/aws_organizational_units/variables.tf create mode 100644 aws_organization/modules/aws_organizational_units/versions.tf create mode 100644 aws_organization/outputs.tf create mode 100644 aws_organization/variables.tf create mode 100644 aws_organization/versions.tf create mode 100644 aws_s3_backend/README.md create mode 100644 aws_s3_backend/data.tf create mode 100644 aws_s3_backend/locals.tf create mode 100644 aws_s3_backend/main.tf create mode 100644 aws_s3_backend/outputs.tf create mode 100644 aws_s3_backend/templates/dynamodb_access_policy.tftpl create mode 100644 aws_s3_backend/templates/iam_role_policy.tftpl create mode 100644 aws_s3_backend/templates/s3_bucket_access_policy.tftpl create mode 100644 aws_s3_backend/templates/s3_bucket_policy.tftpl create mode 100644 aws_s3_backend/templates/state_access_iam_role_policy.tftpl create mode 100644 aws_s3_backend/variables.tf create mode 100644 aws_s3_backend/versions.tf create mode 100644 format_data/locals.tf create mode 100644 format_data/outputs.tf create mode 100644 format_data/variables.tf create mode 100644 ses_to_s3_email_forwarder/data.tf create mode 100644 ses_to_s3_email_forwarder/main.tf create mode 100644 ses_to_s3_email_forwarder/outputs.tf create mode 100644 ses_to_s3_email_forwarder/templates/s3_bucket_policy.tftpl create mode 100644 ses_to_s3_email_forwarder/variables.tf create mode 100644 ses_to_s3_email_forwarder/versions.tf create mode 100644 terragrunt_files/locals.tf create mode 100644 terragrunt_files/main.tf create mode 100644 terragrunt_files/templates/env_config.tftpl create mode 100644 terragrunt_files/templates/module_config.tftpl create mode 100644 terragrunt_files/variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b24d71e --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Maven +target/ +dist/ + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + diff --git a/aws_iam_identity_provider/README.md b/aws_iam_identity_provider/README.md new file mode 100644 index 0000000..4672c69 --- /dev/null +++ b/aws_iam_identity_provider/README.md @@ -0,0 +1,58 @@ +# AWS IAM Identity Provider +This module configures the identity provider for the organization. + +## Requirements +* Organization must be created. +* SSO authentication must be enabled within IAM Identity Center. For details on enabling SSO authentication, see [Getting Started](https://docs.aws.amazon.com/singlesignon/latest/userguide/getting-started.html) in the AWS IAM Identity Center (successor to AWS Single Sign-On) User Guide. +* External identity provider must be set within IAM Identity Center (if you are using external provider for SSO). + +## Usage +```hcl +module aws_iam_identity_provider { + source = "git::ssh://git@bitbucket.org/thesoftwarehouse/terraform-modules-bis.git//aws_iam_identity_provider" + + # Configuration options +} +``` +## Examples +### Create a 'AdministratorAccess' predefined permission set +```hcl +module aws_iam_identity_provider { + source = "git::ssh://git@bitbucket.org/thesoftwarehouse/terraform-modules-bis.git//aws_iam_identity_provider" + + permision_sets = { + "AdministratorAccess" = { + session_duration = "PT1H" + predefined_permission_set = "AdministratorAccess" + } + } +} +``` + +### Create a custom permission set with permission to describe EC2 instances +```hcl +module aws_iam_identity_provider { + source = "git::ssh://git@bitbucket.org/thesoftwarehouse/terraform-modules-bis.git//aws_iam_identity_provider" + + permission_sets = { + "DescribeEC2" = { + session_duration = "PT2H" + relay_state = "https://eu-central-1.console.aws.amazon.com/ec2/" + description = "This policy grants permissions to describe EC2 instances." + + custom_permission_set = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "ec2:Describe*", + ] + Effect = "Allow" + Resource = "*" + }, + ] + }) + } + } +} +``` \ No newline at end of file diff --git a/aws_iam_identity_provider/locals.tf b/aws_iam_identity_provider/locals.tf new file mode 100644 index 0000000..0a81492 --- /dev/null +++ b/aws_iam_identity_provider/locals.tf @@ -0,0 +1,50 @@ +locals { + default_session_duration = "PT1H" + + predefined_permission_sets = { for name, permission_set in var.permission_sets : name => { + predefined_permission_set = permission_set.predefined_permission_set + description = permission_set.description != null ? permission_set.description : local.predefined_permission_set_definitions[permission_set.predefined_permission_set].description + relay_state = permission_set.relay_state + session_duration = permission_set.session_duration != null ? permission_set.session_duration : local.default_session_duration + } if try(permission_set.predefined_permission_set, null) != null } + + custom_permission_sets = { for name, permission_set in var.permission_sets : name => { + custom_permission_set = permission_set.custom_permission_set + description = permission_set.description + relay_state = permission_set.relay_state + session_duration = permission_set.session_duration != null ? permission_set.session_duration : local.default_session_duration + } if try(permission_set.custom_permission_set, null) != null } + + predefined_permission_set_definitions = { + "AdministratorAccess" = { + description = "Provides full access to AWS services and resources." + } + "Billing" = { + description = "Grants permissions for billing and cost management. This includes viewing account usage and viewing and modifying budgets and payment methods." + } + "DatabaseAdministrator" = { + description = "Grants full access permissions to AWS services and actions required to set up and configure AWS database services." + } + "DataScientist" = { + description = "Grants permissions to AWS data analytics services." + } + "NetworkAdministrator" = { + description = "Grants full access permissions to AWS services and actions required to set up and configure AWS network resources." + } + "PowerUserAccess" = { + description = "Provides full access to AWS services and resources, but does not allow management of Users and groups." + } + "SecurityAudit" = { + description = "The security audit template grants access to read security configuration metadata. It is useful for software that audits the configuration of an AWS account." + } + "SupportUser" = { + description = "This policy grants permissions to troubleshoot and resolve issues in an AWS account. This policy also enables the user to contact AWS support to create and manage cases." + } + "SystemAdministrator" = { + description = "Grants full access permissions necessary for resources required for application and development operations." + } + "ViewOnlyAccess" = { + description = "This policy grants permissions to view resources and basic metadata across all AWS services." + } + } +} \ No newline at end of file diff --git a/aws_iam_identity_provider/main.tf b/aws_iam_identity_provider/main.tf new file mode 100644 index 0000000..0171f28 --- /dev/null +++ b/aws_iam_identity_provider/main.tf @@ -0,0 +1,50 @@ +data "aws_ssoadmin_instances" "this" {} + +# Predefined permision set +data "aws_iam_policy" "predefined" { + for_each = local.predefined_permission_set_definitions + name = each.key +} + +resource "aws_ssoadmin_permission_set" "predefined" { + for_each = local.predefined_permission_sets + name = each.key + description = each.value["description"] + relay_state = each.value["relay_state"] + session_duration = each.value["session_duration"] + instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0] +} + +resource "aws_ssoadmin_managed_policy_attachment" "predefined" { + for_each = local.predefined_permission_sets + instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0] + managed_policy_arn = data.aws_iam_policy.predefined[each.value["predefined_permission_set"]].arn + permission_set_arn = aws_ssoadmin_permission_set.predefined[each.key].arn +} + +# Custom permission set +resource "aws_iam_policy" "custom" { + for_each = local.custom_permission_sets + name = format("%s_PermissionSet", each.key) + description = each.value["description"] + policy = each.value["custom_permission_set"] +} + +resource "aws_ssoadmin_permission_set" "custom" { + for_each = local.custom_permission_sets + name = each.key + description = each.value["description"] + relay_state = each.value["relay_state"] + session_duration = each.value["session_duration"] + instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0] +} + +resource "aws_ssoadmin_customer_managed_policy_attachment" "custom" { + for_each = local.custom_permission_sets + instance_arn = aws_ssoadmin_permission_set.custom[each.key].instance_arn + permission_set_arn = aws_ssoadmin_permission_set.custom[each.key].arn + customer_managed_policy_reference { + name = aws_iam_policy.custom[each.key].name + path = "/" + } +} \ No newline at end of file diff --git a/aws_iam_identity_provider/outputs.tf b/aws_iam_identity_provider/outputs.tf new file mode 100644 index 0000000..24c7c68 --- /dev/null +++ b/aws_iam_identity_provider/outputs.tf @@ -0,0 +1,4 @@ +output "permission_set_arns" { + value = merge({ for i in aws_ssoadmin_permission_set.predefined : i.name => i.arn }, { for i in aws_ssoadmin_permission_set.custom : i.name => i.arn }) + description = "A map of permission set ARNs." +} \ No newline at end of file diff --git a/aws_iam_identity_provider/variables.tf b/aws_iam_identity_provider/variables.tf new file mode 100644 index 0000000..cea45da --- /dev/null +++ b/aws_iam_identity_provider/variables.tf @@ -0,0 +1,34 @@ +variable "permission_sets" { + type = map(object( + { + description = optional(string) + relay_state = optional(string) + session_duration = optional(string) + predefined_permission_set = optional(string) + custom_permission_set = optional(string) + } + )) + description = "A map of permission sets to be created in the organization." + + validation { + condition = alltrue([for i in var.permission_sets : i.custom_permission_set == null if try(i.predefined_permission_set, null) != null]) + error_message = "A predefined_permission_set cannot be set together with a custom_permission_set in the same object." + } + + # Predefined permision set + validation { # Predefined permission set must be one of the AWS managed policy (predefined permission set list) + condition = alltrue([for i in var.permission_sets : contains(["AdministratorAccess", "Billing", "DatabaseAdministrator", "DataScientist", "NetworkAdministrator", "PowerUserAccess", "SecurityAudit", "SupportUser", "SystemAdministrator", "ViewOnlyAccess"], i.predefined_permission_set) if try(i.predefined_permission_set, null) != null]) + error_message = "A predefined_permission_set value must be one of the following: 'AdministratorAccess', 'Billing', 'DatabaseAdministrator', 'DataScientist', 'NetworkAdministrator', 'PowerUserAccess', 'SecurityAudit', 'SupportUser', 'SystemAdministrator', 'ViewOnlyAccess'." + } + + validation { + condition = length([for i in var.permission_sets : i.predefined_permission_set if try(i.predefined_permission_set, null) != null]) == length(distinct([for i in var.permission_sets : i.predefined_permission_set if try(i.predefined_permission_set, null) != null])) + error_message = "All predefined_permission_set values must be unique." + } + + # Custom permision set + validation { # Policy must be JSON code + condition = alltrue([for i in var.permission_sets : can(jsondecode(i.custom_permission_set)) if try(i.custom_permission_set, null) != null]) + error_message = "A custom_permission_set value must be valid JSON code." + } +} \ No newline at end of file diff --git a/aws_identity_providers/README.md b/aws_identity_providers/README.md new file mode 100644 index 0000000..e69de29 diff --git a/aws_identity_providers/modules/bitbucket_oidc/README.md b/aws_identity_providers/modules/bitbucket_oidc/README.md new file mode 100644 index 0000000..1d9ea3c --- /dev/null +++ b/aws_identity_providers/modules/bitbucket_oidc/README.md @@ -0,0 +1,24 @@ +# AWS Bitbucket OIDC Provider + +Terraform module which creates OIDC provider and access roles for specific Bitbucket repositories. + +## Usage + +```hcl +module "aws-bitbucket-oidc-provider" { + source = "git::ssh://git@bitbucket.org/thesoftwarehouse/terraform-modules.git//aws_bitbucket_oidc_provider?ref=main" + + # Open a Bitbucket repository and go to Repository settings -> OpenID Connect in the Pipelines section to get all the details. + identity_provider_url = "" + audience = "" + + thumbprints = [""] + + roles = { + "" = { + create = true + repository_uuids = ["{}"] + } + } +} +``` \ No newline at end of file diff --git a/aws_identity_providers/modules/bitbucket_oidc/json/bitbucket_oidc_policy.json b/aws_identity_providers/modules/bitbucket_oidc/json/bitbucket_oidc_policy.json new file mode 100644 index 0000000..8e577cc --- /dev/null +++ b/aws_identity_providers/modules/bitbucket_oidc/json/bitbucket_oidc_policy.json @@ -0,0 +1,19 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AssumeRole", + "Effect": "Allow", + "Action": [ + "sts:GetSessionToken", + "sts:AssumeRole", + "sts:GetFederationToken", + "sts:GetAccessKeyInfo", + "sts:GetCallerIdentity", + "sts:GetServiceBearerToken", + "sts:AssumeRoleWithWebIdentity" + ], + "Resource": "*" + } + ] +} \ No newline at end of file diff --git a/aws_identity_providers/modules/bitbucket_oidc/locals.tf b/aws_identity_providers/modules/bitbucket_oidc/locals.tf new file mode 100644 index 0000000..33e8646 --- /dev/null +++ b/aws_identity_providers/modules/bitbucket_oidc/locals.tf @@ -0,0 +1,9 @@ +locals { + common = { + tags = { + created_by = "Terraform" + } + } + + bitbucket_oidc_policy = file("${path.module}/json/bitbucket_oidc_policy.json") +} \ No newline at end of file diff --git a/aws_identity_providers/modules/bitbucket_oidc/main.tf b/aws_identity_providers/modules/bitbucket_oidc/main.tf new file mode 100644 index 0000000..c7a4aaf --- /dev/null +++ b/aws_identity_providers/modules/bitbucket_oidc/main.tf @@ -0,0 +1,43 @@ +module "iam_iam-assumable-role-with-oidc" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "5.8.0" + for_each = var.roles + + create_role = true + role_name = each.key # ^[\w+=,.@-]{1,64}$ + role_description = "Role that can be assumed using Bitbucket Pipelines with OIDC provider." + aws_account_id = data.aws_caller_identity.current.account_id + provider_url = trimprefix(var.identity_providers[each.value["provider"]].identity_provider_url, "https://") + role_policy_arns = [module.iam_iam-policy.arn] + oidc_subjects_with_wildcards = formatlist("%s:*", each.value["repository_uuids"]) + + # tags = merge( + # local.common.tags, + # { + # Project = each.key + # } + # ) +} + +data "aws_caller_identity" "current" {} + +resource "aws_iam_openid_connect_provider" "this" { + for_each = var.identity_providers + url = each.value["identity_provider_url"] + client_id_list = [each.value["audience"]] + thumbprint_list = each.value["thumbprints"] + + # tags = local.common.tags +} + +module "iam_iam-policy" { + source = "terraform-aws-modules/iam/aws//modules/iam-policy" + version = "5.9.0" + + create_policy = true + name = "BitbucketOIDCPolicy" + description = "Allows to assume a role by BitbucketOIDC roles." + path = "/" + policy = local.bitbucket_oidc_policy + # tags = local.common.tags +} \ No newline at end of file diff --git a/aws_identity_providers/modules/bitbucket_oidc/variables.tf b/aws_identity_providers/modules/bitbucket_oidc/variables.tf new file mode 100644 index 0000000..8afdbdb --- /dev/null +++ b/aws_identity_providers/modules/bitbucket_oidc/variables.tf @@ -0,0 +1,35 @@ +variable "identity_providers" { + type = map(object( + { + identity_provider_url = string + audience = string + thumbprints = list(string) + } + )) + description = "" + + validation { + condition = alltrue([for provider in var.identity_providers : startswith(provider.identity_provider_url, "https://")]) + error_message = "The identity_provider_url must begin with 'https://'." + } + + validation { + condition = alltrue([for provider in var.identity_providers : length(provider.thumbprints) <= 5 && length(provider.thumbprints) == length(distinct(provider.thumbprints))]) + error_message = "All thumbprints must be unique. The maximum number is 5." + } +} + +variable "roles" { + type = map(object( + { + provider = string + repository_uuids = list(string) + } + )) + description = "" + + validation { + condition = alltrue([for role in var.roles : can([for repo in role.repository_uuids : regex("^{([0-9a-z]+[-])+([0-9a-z]+)}$", repo)])]) + error_message = "The repository_uuid must consist of characters in the ranges [a-z], [0-9] and '-'. The repository_uuid must start with '{' and end with '}' characters." + } +} \ No newline at end of file diff --git a/aws_identity_providers/modules/bitbucket_oidc/versions.tf b/aws_identity_providers/modules/bitbucket_oidc/versions.tf new file mode 100644 index 0000000..9266874 --- /dev/null +++ b/aws_identity_providers/modules/bitbucket_oidc/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4" + } + } +} \ No newline at end of file diff --git a/aws_organization/README.md b/aws_organization/README.md new file mode 100644 index 0000000..80bc886 --- /dev/null +++ b/aws_organization/README.md @@ -0,0 +1 @@ +# AWS Organization \ No newline at end of file diff --git a/aws_organization/locals.tf b/aws_organization/locals.tf new file mode 100644 index 0000000..1f1bbda --- /dev/null +++ b/aws_organization/locals.tf @@ -0,0 +1,36 @@ +locals { + aws_service_access_principals = [ + "access-analyzer.amazonaws.com", + "auditmanager.amazonaws.com", + "aws-artifact-account-sync.amazonaws.com", + "backup.amazonaws.com", + "cloudtrail.amazonaws.com", + "compute-optimizer.amazonaws.com", + "config-multiaccountsetup.amazonaws.com", + "config.amazonaws.com", + "controltower.amazonaws.com", + "ds.amazonaws.com", + "fms.amazonaws.com", + "guardduty.amazonaws.com", + "license-manager.amazonaws.com", + "license-manager.member-account.amazonaws.com", + "macie.amazonaws.com", + "member.org.stacksets.cloudformation.amazonaws.com", + "ram.amazonaws.com", + "reporting.trustedadvisor.amazonaws.com", + "securityhub.amazonaws.com", + "servicecatalog.amazonaws.com", + "servicequotas.amazonaws.com", + "ssm.amazonaws.com", + "sso.amazonaws.com", + "storage-lens.s3.amazonaws.com", + "tagpolicies.tag.amazonaws.com", + ] + + enabled_policy_types = [ + "AISERVICES_OPT_OUT_POLICY", + "BACKUP_POLICY", + "SERVICE_CONTROL_POLICY", + "TAG_POLICY", + ] +} diff --git a/aws_organization/main.tf b/aws_organization/main.tf new file mode 100644 index 0000000..8344cc4 --- /dev/null +++ b/aws_organization/main.tf @@ -0,0 +1,18 @@ +resource "aws_organizations_organization" "this" { + aws_service_access_principals = local.aws_service_access_principals + enabled_policy_types = local.enabled_policy_types + + feature_set = "ALL" +} + +module "aws_organizational_units" { + source = "./modules/aws_organizational_units" + organizational_units = var.organizational_units + organization_root_id = aws_organizations_organization.this.roots[0].id +} + +module "aws_accounts" { + source = "./modules/aws_accounts" + accounts = var.accounts + ou_ids = module.aws_organizational_units.ou_ids +} \ No newline at end of file diff --git a/aws_organization/modules/aws_account_alias/README.md b/aws_organization/modules/aws_account_alias/README.md new file mode 100644 index 0000000..e69de29 diff --git a/aws_organization/modules/aws_account_alias/locals.tf b/aws_organization/modules/aws_account_alias/locals.tf new file mode 100644 index 0000000..fcf78e6 --- /dev/null +++ b/aws_organization/modules/aws_account_alias/locals.tf @@ -0,0 +1,4 @@ +locals { + alias = substr(var.alias, 0, length(var.alias_prefix) + 1) == format("%s-", var.alias_prefix) ? trimprefix(var.alias, format("%s-", var.alias_prefix)) : var.alias + account_alias = lower(substr(join("-", compact([var.alias_prefix, local.alias, var.alias_suffix])), 0, 31)) +} diff --git a/aws_organization/modules/aws_account_alias/main.tf b/aws_organization/modules/aws_account_alias/main.tf new file mode 100644 index 0000000..3b429f8 --- /dev/null +++ b/aws_organization/modules/aws_account_alias/main.tf @@ -0,0 +1,3 @@ +resource "aws_iam_account_alias" "this" { + account_alias = local.account_alias +} \ No newline at end of file diff --git a/aws_organization/modules/aws_account_alias/outputs.tf b/aws_organization/modules/aws_account_alias/outputs.tf new file mode 100644 index 0000000..11ee50e --- /dev/null +++ b/aws_organization/modules/aws_account_alias/outputs.tf @@ -0,0 +1,3 @@ +output "account_alias" { + value = aws_iam_account_alias.this.account_alias +} \ No newline at end of file diff --git a/aws_organization/modules/aws_account_alias/variables.tf b/aws_organization/modules/aws_account_alias/variables.tf new file mode 100644 index 0000000..e19090a --- /dev/null +++ b/aws_organization/modules/aws_account_alias/variables.tf @@ -0,0 +1,31 @@ +variable "alias" { + type = string + description = "AWS account alias name. If the alias, prefix and suffix together exceed 31 characters, the alias will be shortened to 31 characters by truncating the excess ending." + + validation { + condition = can(regex("^[0-9a-z]{1}([0-9a-z-]?[0-9a-z]{1}){1,31}$", var.alias)) + error_message = "Account alias must be unique across AWS products and must be alphanumeric. An alias must be lowercase, it must not start or end with a hyphen, it cannot contain two consecutive hyphens and it cannot be a 12-digit number. The minimum number of characters is 2 and maximum is 31." + } +} + +variable "alias_prefix" { + type = string + default = "" + description = "AWS account alias prefix. If the alias, prefix and suffix together exceed 31 characters, the alias will be shortened to 31 characters by truncating the excess ending." + + validation { + condition = can(regex("^[0-9a-z]{0,10}$", var.alias_prefix)) + error_message = "Account alias prefix must be alphanumeric and must be lowercase. The maximum number of characters is 10." + } +} + +variable "alias_suffix" { + type = string + default = "" + description = "AWS account alias suffix. If the alias, prefix and suffix together exceed 31 characters, the alias will be shortened to 31 characters by truncating the excess ending." + + validation { + condition = can(regex("^[0-9a-z]{0,10}$", var.alias_suffix)) + error_message = "Account alias suffix must be alphanumeric and must be lowercase. The maximum number of characters is 10." + } +} \ No newline at end of file diff --git a/aws_organization/modules/aws_accounts/README.md b/aws_organization/modules/aws_accounts/README.md new file mode 100644 index 0000000..6c325f5 --- /dev/null +++ b/aws_organization/modules/aws_accounts/README.md @@ -0,0 +1,3 @@ +# AWS Accounts + +Must be used in the Organization's management account \ No newline at end of file diff --git a/aws_organization/modules/aws_accounts/main.tf b/aws_organization/modules/aws_accounts/main.tf new file mode 100644 index 0000000..e227c1f --- /dev/null +++ b/aws_organization/modules/aws_accounts/main.tf @@ -0,0 +1,13 @@ +resource "aws_organizations_account" "this" { + for_each = var.accounts + name = each.key + email = each.value["email"] + role_name = var.organization_account_access_role_name + parent_id = lookup(var.ou_ids, each.value["ou"]) + close_on_deletion = each.value["close_on_deletion"] + tags = each.value["tags"] + + lifecycle { + ignore_changes = [role_name] + } +} \ No newline at end of file diff --git a/aws_organization/modules/aws_accounts/outputs.tf b/aws_organization/modules/aws_accounts/outputs.tf new file mode 100644 index 0000000..c40c530 --- /dev/null +++ b/aws_organization/modules/aws_accounts/outputs.tf @@ -0,0 +1,3 @@ +output "account_ids" { + value = { for name, account in aws_organizations_account.this : name => account.id } +} \ No newline at end of file diff --git a/aws_organization/modules/aws_accounts/variables.tf b/aws_organization/modules/aws_accounts/variables.tf new file mode 100644 index 0000000..360d322 --- /dev/null +++ b/aws_organization/modules/aws_accounts/variables.tf @@ -0,0 +1,28 @@ +variable "accounts" { + type = map(object( + { + email = string + ou = string + close_on_deletion = optional(bool) + tags = optional(map(string)) + } + )) + + description = "A map of accounts ..." + + validation { + condition = alltrue([for account_name, account in var.accounts : can(regex("^[\\w]{1}([\\w\\s/_-]{0,1}[\\w]{1}){1,31}$", account_name))]) + error_message = "The account name must consist of characters in the ranges [0-9a-zA-Z/_-] and whitespaces. It must start and end with an alphanumeric characters. It cannot contain two consecutive characters in the range [/_-] and whitespaces." + } +} + +variable "ou_ids" { + type = map(string) + description = "description" +} + +variable "organization_account_access_role_name" { + type = string + default = "OrganizationAccountAccessRole" + description = "description" +} \ No newline at end of file diff --git a/aws_organization/modules/aws_accounts/versions.tf b/aws_organization/modules/aws_accounts/versions.tf new file mode 100644 index 0000000..a0d7fed --- /dev/null +++ b/aws_organization/modules/aws_accounts/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + local = { + source = "hashicorp/local" + version = ">= 2.2.3" + } + aws = { + source = "hashicorp/aws" + version = ">= 4.45" + } + } +} \ No newline at end of file diff --git a/aws_organization/modules/aws_get_organization_details/README.md b/aws_organization/modules/aws_get_organization_details/README.md new file mode 100644 index 0000000..c1f2347 --- /dev/null +++ b/aws_organization/modules/aws_get_organization_details/README.md @@ -0,0 +1,41 @@ +# AWS Get Organization Details + +## Requirements +- A provider must assume a role in the AWS Organization's management account. + +## Usage +### The default provider has assumed a role in the AWS Organization's management account +```hcl +provider "aws" { + assume_role { + role_arn = "arn:aws:iam:::role/" + } +} + +module aws_get_organizational_units { + source = ".../aws_organization/modules//aws_get_organizational_units" +} +``` + +### Multiple providers. The default one did not assume the role in the AWS Organization's management account +```hcl +provider "aws" { + assume_role { + role_arn = "arn:aws:iam:::role/" + } +} + +provider "aws" { + alias = "management" + assume_role { + role_arn = "arn:aws:iam:::role/" + } +} + +module aws_get_organizational_units { + source = ".../aws_organization/modules//aws_get_organizational_units" + providers = { + aws = aws.management + } +} +``` \ No newline at end of file diff --git a/aws_organization/modules/aws_get_organization_details/data.tf b/aws_organization/modules/aws_get_organization_details/data.tf new file mode 100644 index 0000000..0eee0c6 --- /dev/null +++ b/aws_organization/modules/aws_get_organization_details/data.tf @@ -0,0 +1,32 @@ +# Organizational Units +data "aws_organizations_organization" "this" {} + +data "aws_organizations_organizational_units" "root" { + for_each = local.root + parent_id = each.value +} + +data "aws_organizations_organizational_units" "level_1" { + for_each = local.level_1 + parent_id = each.value +} + +data "aws_organizations_organizational_units" "level_2" { + for_each = local.level_2 + parent_id = each.value +} + +data "aws_organizations_organizational_units" "level_3" { + for_each = local.level_3 + parent_id = each.value +} + +data "aws_organizations_organizational_units" "level_4" { + for_each = local.level_4 + parent_id = each.value +} + +data "aws_organizations_organizational_units" "level_5" { + for_each = local.level_5 + parent_id = each.value +} \ No newline at end of file diff --git a/aws_organization/modules/aws_get_organization_details/locals.tf b/aws_organization/modules/aws_get_organization_details/locals.tf new file mode 100644 index 0000000..d2aea5b --- /dev/null +++ b/aws_organization/modules/aws_get_organization_details/locals.tf @@ -0,0 +1,28 @@ +locals { + # Organizational Units + list_of_roots = flatten([for i in data.aws_organizations_organization.this.roots : { name = i.name, id = i.id }]) + list_of_level_1 = flatten([for i in data.aws_organizations_organizational_units.root : [for j in i.children : { name = j.name, id = j.id }]]) + list_of_level_2 = flatten([for i in data.aws_organizations_organizational_units.level_1 : [for j in i.children : { name = j.name, id = j.id }]]) + list_of_level_3 = flatten([for i in data.aws_organizations_organizational_units.level_2 : [for j in i.children : { name = j.name, id = j.id }]]) + list_of_level_4 = flatten([for i in data.aws_organizations_organizational_units.level_3 : [for j in i.children : { name = j.name, id = j.id }]]) + list_of_level_5 = flatten([for i in data.aws_organizations_organizational_units.level_4 : [for j in i.children : { name = j.name, id = j.id }]]) + + root = { for root in local.list_of_roots : root.name => root.id } + level_1 = { for ou in local.list_of_level_1 : ou.name => ou.id } + level_2 = { for ou in local.list_of_level_2 : ou.name => ou.id } + level_3 = { for ou in local.list_of_level_3 : ou.name => ou.id } + level_4 = { for ou in local.list_of_level_4 : ou.name => ou.id } + level_5 = { for ou in local.list_of_level_5 : ou.name => ou.id } + + ou_ids = merge( + local.root, + local.level_1, + local.level_2, + local.level_3, + local.level_4, + local.level_5 + ) + + # Accounts + account_ids = { for account in data.aws_organizations_organization.this.accounts : account.name => account.id } +} \ No newline at end of file diff --git a/aws_organization/modules/aws_get_organization_details/outputs.tf b/aws_organization/modules/aws_get_organization_details/outputs.tf new file mode 100644 index 0000000..2dcf3fe --- /dev/null +++ b/aws_organization/modules/aws_get_organization_details/outputs.tf @@ -0,0 +1,41 @@ +# Organizational Units +output "ou_ids" { + value = local.ou_ids + description = "All levels Organizational Unit IDs." +} + +output "root_ids" { + value = local.root + description = "" +} + +output "l1_ou_ids" { + value = local.level_1 + description = "1st level Organizational Unit IDs." +} + +output "l2_ou_ids" { + value = local.level_2 + description = "2nd level Organizational Unit IDs." +} + +output "l3_ou_ids" { + value = local.level_3 + description = "3rd level Organizational Unit IDs." +} + +output "l4_ou_ids" { + value = local.level_4 + description = "4th level Organizational Unit IDs." +} + +output "l5_ou_ids" { + value = local.level_5 + description = "5th level Organizational Unit IDs." +} + +# Accounts +output "account_ids" { + value = local.account_ids + description = "All account IDs." +} \ No newline at end of file diff --git a/aws_organization/modules/aws_get_organization_details/versions.tf b/aws_organization/modules/aws_get_organization_details/versions.tf new file mode 100644 index 0000000..547a537 --- /dev/null +++ b/aws_organization/modules/aws_get_organization_details/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.45" + } + } +} \ No newline at end of file diff --git a/aws_organization/modules/aws_organizational_units/README.md b/aws_organization/modules/aws_organizational_units/README.md new file mode 100644 index 0000000..5e2e086 --- /dev/null +++ b/aws_organization/modules/aws_organizational_units/README.md @@ -0,0 +1,28 @@ +# AWS Organizational Units + +Zmiana wartości argumentu `parent_id` w `aws_organizations_organizational_unit` wymusza usunięcie jednostki organizacyjnej i utworzenie nowej. + +## Usage + +```hcl +module "aws-bitbucket-oidc-provider" { + source = "git::ssh://git@bitbucket.org/thesoftwarehouse/terraform-modules.git//aws_organizational_units?ref=main" + + organizational_units = [ + { + name = "" # level 1 + childs = [ + { + name = "" # level 2 + childs = [ + { name = "" } # level 3 + ] + }, + { name = "" } # level 2 + ] + }, + { + name = "" # level 1 + } + ] +``` \ No newline at end of file diff --git a/aws_organization/modules/aws_organizational_units/locals.tf b/aws_organization/modules/aws_organizational_units/locals.tf new file mode 100644 index 0000000..616ab43 --- /dev/null +++ b/aws_organization/modules/aws_organizational_units/locals.tf @@ -0,0 +1,13 @@ +locals { + list_of_level_1 = flatten([for i in var.organizational_units : { name = i.name, parent_ou = "root" }]) + list_of_level_2 = flatten([for i in var.organizational_units : [for j in lookup(i, "children", []) : { name = j.name, parent_ou = i.name }]]) + list_of_level_3 = flatten([for i in var.organizational_units : [for j in lookup(i, "children", []) : [for k in lookup(j, "children", []) : { name = k.name, parent_ou = j.name }]]]) + list_of_level_4 = flatten([for i in var.organizational_units : [for j in lookup(i, "children", []) : [for k in lookup(j, "children", []) : [for l in lookup(k, "children", []) : { name = l.name, parent_ou = k.name }]]]]) + list_of_level_5 = flatten([for i in var.organizational_units : [for j in lookup(i, "children", []) : [for k in lookup(j, "children", []) : [for l in lookup(k, "children", []) : [for m in lookup(l, "children", []) : { name = m.name, parent_ou = l.name }]]]]]) + + level_1 = { for ou in local.list_of_level_1 : ou.name => { parent_id = ou.parent_ou, tags = lookup(ou, "tags", {}) } } + level_2 = { for ou in local.list_of_level_2 : ou.name => { parent_id = "${aws_organizations_organizational_unit.level_1[ou.parent_ou].id}", tags = lookup(ou, "tags", {}) } } + level_3 = { for ou in local.list_of_level_3 : ou.name => { parent_id = "${aws_organizations_organizational_unit.level_2[ou.parent_ou].id}", tags = lookup(ou, "tags", {}) } } + level_4 = { for ou in local.list_of_level_4 : ou.name => { parent_id = "${aws_organizations_organizational_unit.level_3[ou.parent_ou].id}", tags = lookup(ou, "tags", {}) } } + level_5 = { for ou in local.list_of_level_5 : ou.name => { parent_id = "${aws_organizations_organizational_unit.level_4[ou.parent_ou].id}", tags = lookup(ou, "tags", {}) } } +} \ No newline at end of file diff --git a/aws_organization/modules/aws_organizational_units/main.tf b/aws_organization/modules/aws_organizational_units/main.tf new file mode 100644 index 0000000..0d82fbb --- /dev/null +++ b/aws_organization/modules/aws_organizational_units/main.tf @@ -0,0 +1,42 @@ +resource "aws_organizations_organizational_unit" "level_1" { + for_each = local.level_1 + name = each.key + parent_id = each.value["parent_id"] != "root" ? each.value["parent_id"] : var.organization_root_id + tags = each.value["tags"] +} + +resource "aws_organizations_organizational_unit" "level_2" { + for_each = local.level_2 + name = each.key + parent_id = each.value["parent_id"] + tags = each.value["tags"] + + depends_on = [aws_organizations_organizational_unit.level_1] +} + +resource "aws_organizations_organizational_unit" "level_3" { + for_each = local.level_3 + name = each.key + parent_id = each.value["parent_id"] + tags = each.value["tags"] + + depends_on = [aws_organizations_organizational_unit.level_2] +} + +resource "aws_organizations_organizational_unit" "level_4" { + for_each = local.level_4 + name = each.key + parent_id = each.value["parent_id"] + tags = each.value["tags"] + + depends_on = [aws_organizations_organizational_unit.level_3] +} + +resource "aws_organizations_organizational_unit" "level_5" { + for_each = local.level_5 + name = each.key + parent_id = each.value["parent_id"] + tags = each.value["tags"] + + depends_on = [aws_organizations_organizational_unit.level_4] +} \ No newline at end of file diff --git a/aws_organization/modules/aws_organizational_units/outputs.tf b/aws_organization/modules/aws_organizational_units/outputs.tf new file mode 100644 index 0000000..1a8d004 --- /dev/null +++ b/aws_organization/modules/aws_organizational_units/outputs.tf @@ -0,0 +1,11 @@ +output "ou_ids" { + value = merge( + { for name, ou in aws_organizations_organizational_unit.level_1 : name => ou.id }, + { for name, ou in aws_organizations_organizational_unit.level_2 : name => ou.id }, + { for name, ou in aws_organizations_organizational_unit.level_3 : name => ou.id }, + { for name, ou in aws_organizations_organizational_unit.level_4 : name => ou.id }, + { for name, ou in aws_organizations_organizational_unit.level_5 : name => ou.id } + ) + # sensitive = true + description = "description" +} \ No newline at end of file diff --git a/aws_organization/modules/aws_organizational_units/variables.tf b/aws_organization/modules/aws_organizational_units/variables.tf new file mode 100644 index 0000000..490bbba --- /dev/null +++ b/aws_organization/modules/aws_organizational_units/variables.tf @@ -0,0 +1,9 @@ +variable "organizational_units" { + type = list(any) + description = "A list of Organizational Units with their children. Maximum nesting is limited to 5 levels." +} + +variable "organization_root_id" { + type = string + default = "" +} \ No newline at end of file diff --git a/aws_organization/modules/aws_organizational_units/versions.tf b/aws_organization/modules/aws_organizational_units/versions.tf new file mode 100644 index 0000000..547a537 --- /dev/null +++ b/aws_organization/modules/aws_organizational_units/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.45" + } + } +} \ No newline at end of file diff --git a/aws_organization/outputs.tf b/aws_organization/outputs.tf new file mode 100644 index 0000000..a49dcc2 --- /dev/null +++ b/aws_organization/outputs.tf @@ -0,0 +1,7 @@ +output "ou_ids" { + value = module.aws_organizational_units.ou_ids +} + +output "account_ids" { + value = module.aws_accounts.account_ids +} \ No newline at end of file diff --git a/aws_organization/variables.tf b/aws_organization/variables.tf new file mode 100644 index 0000000..c1503bc --- /dev/null +++ b/aws_organization/variables.tf @@ -0,0 +1,19 @@ +# aws_organizational_units +variable "organizational_units" { + type = list(any) + description = "A nested list of Organizational Units to create in the organization." +} + +# aws_accounts +variable "accounts" { + type = map(object( + { + email = string + ou = string + close_on_deletion = optional(bool) + tags = optional(map(string)) + } + )) + + description = "" +} \ No newline at end of file diff --git a/aws_organization/versions.tf b/aws_organization/versions.tf new file mode 100644 index 0000000..547a537 --- /dev/null +++ b/aws_organization/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.45" + } + } +} \ No newline at end of file diff --git a/aws_s3_backend/README.md b/aws_s3_backend/README.md new file mode 100644 index 0000000..f63a98d --- /dev/null +++ b/aws_s3_backend/README.md @@ -0,0 +1 @@ +# AWS Terraform S3 Backend \ No newline at end of file diff --git a/aws_s3_backend/data.tf b/aws_s3_backend/data.tf new file mode 100644 index 0000000..4812369 --- /dev/null +++ b/aws_s3_backend/data.tf @@ -0,0 +1,5 @@ +data "aws_caller_identity" "current" {} + +data "aws_organizations_organization" "this" { + provider = aws.management +} \ No newline at end of file diff --git a/aws_s3_backend/locals.tf b/aws_s3_backend/locals.tf new file mode 100644 index 0000000..73b5195 --- /dev/null +++ b/aws_s3_backend/locals.tf @@ -0,0 +1,21 @@ +locals { + account_ids = { for account in data.aws_organizations_organization.this.accounts : account.name => account.id } + + backends = { for backend_key, backend in var.backends : backend_key => { + account_ids = [for account in backend.account_names : lookup(local.account_ids, account)] + } } + + + + s3_bucket_policies = { for backend_key, backend in local.backends : backend_key => templatefile("${path.module}/templates/s3_bucket_policy.tftpl", { + root_name = backend_key + }) } + + iam_role_policies = { for backend_key, backend in local.backends : backend_key => templatefile("${path.module}/templates/iam_role_policy.tftpl", { + account_arns = jsonencode([ + "arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*/AWSReservedSSO_AdministratorAccess_*", + "arn:aws:iam::*:role/BitbucketOIDC_AdministratorAccess" + ]) + account_ids = jsonencode(backend.account_ids) + }) } +} \ No newline at end of file diff --git a/aws_s3_backend/main.tf b/aws_s3_backend/main.tf new file mode 100644 index 0000000..7083f35 --- /dev/null +++ b/aws_s3_backend/main.tf @@ -0,0 +1,150 @@ +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "3.6.0" + for_each = local.backends + + force_destroy = true + bucket = format("tsh-%s-state", lower(replace(each.key, "/\\s+/", "-"))) + acl = "private" + restrict_public_buckets = true + + versioning = { + enabled = true + } + server_side_encryption_configuration = {} + replication_configuration = {} + + policy = jsonencode(templatefile("${path.module}/templates/s3_bucket_policy.tftpl", { + root_name = each.key + # account_id = "ci/cd account id" + })) + + # tags = var.backends[each.key].tags +} + +module "dynamodb_table" { + source = "terraform-aws-modules/dynamodb-table/aws" + version = "3.1.2" + for_each = local.backends + + name = format("tsh-%s-locks", lower(replace(each.key, "/\\s+/", "-"))) + hash_key = "LockID" + + attributes = [ + { + name = "LockID" + type = "S" + } + ] + + # tags = each.value["tags"] +} + +resource "aws_iam_role" "this" { + for_each = local.backends + name = format("TSH_%s_StateAccessRole", replace(each.key, "/\\s+/", "-")) + + # assume_role_policy = jsonencode(templatefile("${path.module}/templates/state_access_iam_role_policy.tftpl", { + # account_ids = "${each.value["account_ids"]}" # nie działa + # })) + + assume_role_policy = jsonencode( + { + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "AWS" : "*" + }, + "Action" : "sts:AssumeRole", + "Condition" : { + "StringLike" : { + "aws:PrincipalARN" : [ + "arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*/AWSReservedSSO_AdministratorAccess_*", + "arn:aws:iam::*:role/BitbucketOIDC_AdministratorAccess" + ] + }, + "StringEquals" : { + "aws:PrincipalAccount" : "${each.value["account_ids"]}" + } + } + } + ] + } + ) + + # tags = each.value["tags"] +} + +resource "aws_iam_policy" "bucket_access" { + for_each = local.backends + name = format("%s_BucketAccessPolicy", replace(each.key, "/\\s+/", "-")) + path = "/" + description = "" + + policy = jsonencode(templatefile("${path.module}/templates/s3_bucket_access_policy.tftpl", { + s3_bucket_arn = module.s3_bucket[each.key].s3_bucket_arn + })) +} + +resource "aws_iam_policy" "dynamodb_access" { + for_each = local.backends + name = format("%s_DynamoDBAccessPolicy", replace(each.key, "/\\s+/", "-")) + path = "/" + description = "" + + # policy = jsonencode(templatefile("${path.module}/templates/dynamodb_access_policy.tftpl", { + # dynamodb_table_arn = module.dynamodb_table[each.key].dynamodb_table_arn + # })) + + policy = jsonencode( + { + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "ListAndDescribe", + "Effect" : "Allow", + "Action" : [ + "dynamodb:List*", + "dynamodb:DescribeReservedCapacity*", + "dynamodb:DescribeLimits", + "dynamodb:DescribeTimeToLive" + ], + "Resource" : "*" + }, + { + "Sid" : "SpecificTable", + "Effect" : "Allow", + "Action" : [ + "dynamodb:BatchGet*", + "dynamodb:DescribeStream", + "dynamodb:DescribeTable", + "dynamodb:Get*", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:BatchWrite*", + "dynamodb:CreateTable", + "dynamodb:Delete*", + "dynamodb:Update*", + "dynamodb:PutItem" + ], + "Resource" : "${module.dynamodb_table[each.key].dynamodb_table_arn}" + } + ] + } + ) +} + +resource "aws_iam_role_policy_attachment" "bucket_access" { + for_each = local.backends + role = aws_iam_role.this[each.key].name + policy_arn = aws_iam_policy.bucket_access[each.key].arn +} + +resource "aws_iam_role_policy_attachment" "dynamodb_access" { + for_each = local.backends + role = aws_iam_role.this[each.key].name + policy_arn = aws_iam_policy.dynamodb_access[each.key].arn +} \ No newline at end of file diff --git a/aws_s3_backend/outputs.tf b/aws_s3_backend/outputs.tf new file mode 100644 index 0000000..c0625d8 --- /dev/null +++ b/aws_s3_backend/outputs.tf @@ -0,0 +1,21 @@ +output "bucket_arns" { + value = { for backend, bucket in module.s3_bucket : backend => bucket.s3_bucket_arn } +} + +output "dynamodb_table_arns" { + value = { for backend, dynamodb_table in module.dynamodb_table : backend => dynamodb_table.dynamodb_table_arn } +} + + +# Temp +output "backends" { + value = local.backends +} + +output "s3_bucket_policies" { + value = local.s3_bucket_policies +} + +output "iam_role_policies" { + value = local.iam_role_policies +} \ No newline at end of file diff --git a/aws_s3_backend/templates/dynamodb_access_policy.tftpl b/aws_s3_backend/templates/dynamodb_access_policy.tftpl new file mode 100644 index 0000000..ace921c --- /dev/null +++ b/aws_s3_backend/templates/dynamodb_access_policy.tftpl @@ -0,0 +1,34 @@ +{ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "ListAndDescribe", + "Effect" : "Allow", + "Action" : [ + "dynamodb:List*", + "dynamodb:DescribeReservedCapacity*", + "dynamodb:DescribeLimits", + "dynamodb:DescribeTimeToLive" + ], + "Resource" : "*" + }, + { + "Sid" : "SpecificTable", + "Effect" : "Allow", + "Action" : [ + "dynamodb:BatchGet*", + "dynamodb:DescribeStream", + "dynamodb:DescribeTable", + "dynamodb:Get*", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:BatchWrite*", + "dynamodb:CreateTable", + "dynamodb:Delete*", + "dynamodb:Update*", + "dynamodb:PutItem" + ], + "Resource" : "${dynamodb_table_arn}" + } + ] + } \ No newline at end of file diff --git a/aws_s3_backend/templates/iam_role_policy.tftpl b/aws_s3_backend/templates/iam_role_policy.tftpl new file mode 100644 index 0000000..61768f4 --- /dev/null +++ b/aws_s3_backend/templates/iam_role_policy.tftpl @@ -0,0 +1,21 @@ +{ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "BitbucketOIDCAccess", + "Effect" : "Allow", + "Principal" : { + "AWS" : "*" + }, + "Action" : "sts:AssumeRole", + "Condition" : { + "StringLike" : { + "aws:PrincipalARN" : ${account_arns} + }, + "StringEquals" : { + "aws:PrincipalAccount" : ${account_ids} + } + } + } + ] +} \ No newline at end of file diff --git a/aws_s3_backend/templates/s3_bucket_access_policy.tftpl b/aws_s3_backend/templates/s3_bucket_access_policy.tftpl new file mode 100644 index 0000000..422f25d --- /dev/null +++ b/aws_s3_backend/templates/s3_bucket_access_policy.tftpl @@ -0,0 +1,27 @@ +{ + "Version" : "2012-10-17", + "Statement" : [ + { + "Effect" : "Allow", + "Action" : [ + "s3:ListAllMyBuckets", + "s3:GetBucketLocation" + ], + "Resource" : "*" + }, + { + "Effect" : "Allow", + "Action" : "s3:ListBucket", + "Resource" : "${s3_bucket_arn}" + }, + { + "Effect" : "Allow", + "Action" : [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + "Resource" : "${s3_bucket_arn}/*" + } + ] + } \ No newline at end of file diff --git a/aws_s3_backend/templates/s3_bucket_policy.tftpl b/aws_s3_backend/templates/s3_bucket_policy.tftpl new file mode 100644 index 0000000..cf2db8f --- /dev/null +++ b/aws_s3_backend/templates/s3_bucket_policy.tftpl @@ -0,0 +1,47 @@ +{ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "EnforcedTLS", + "Effect" : "Deny", + "Principal" : "*", + "Action" : "s3:*", + "Resource" : [ + "arn:aws:s3:::tsh-${root_name}-state", + "arn:aws:s3:::tsh-${root_name}-state/*" + ], + "Condition" : { + "Bool" : { + "aws:SecureTransport" : "false" + } + } + }, + { + "Sid" : "RootAccess", + "Effect" : "Allow", + "Principal" : { + "AWS" : "arn:aws:iam::579187305311:root" + }, + "Action" : "s3:*", + "Resource" : [ + "arn:aws:s3:::tsh-${root_name}-state", + "arn:aws:s3:::tsh-${root_name}-state/*" + ] + }, + { + "Sid" : "AllowTLSRequestsOnly", + "Effect" : "Deny", + "Principal" : "*", + "Action" : "s3:*", + "Resource" : [ + "arn:aws:s3:::tsh-${root_name}-state", + "arn:aws:s3:::tsh-${root_name}-state/*" + ], + "Condition" : { + "Bool" : { + "aws:SecureTransport" : "false" + } + } + } + ] + } \ No newline at end of file diff --git a/aws_s3_backend/templates/state_access_iam_role_policy.tftpl b/aws_s3_backend/templates/state_access_iam_role_policy.tftpl new file mode 100644 index 0000000..7d868f1 --- /dev/null +++ b/aws_s3_backend/templates/state_access_iam_role_policy.tftpl @@ -0,0 +1,24 @@ +{ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "AWS" : "*" + }, + "Action" : "sts:AssumeRole", + "Condition" : { + "StringLike" : { + "aws:PrincipalARN" : [ + "arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*/AWSReservedSSO_AdministratorAccess_*", + "arn:aws:iam::*:role/BitbucketOIDC_AdministratorAccess" + ] + }, + "StringEquals" : { + "aws:PrincipalAccount" : ${account_ids} + } + } + } + ] + } \ No newline at end of file diff --git a/aws_s3_backend/variables.tf b/aws_s3_backend/variables.tf new file mode 100644 index 0000000..f70ceb6 --- /dev/null +++ b/aws_s3_backend/variables.tf @@ -0,0 +1,14 @@ +# variable "backends" { +# type = list(object( +# { +# name = string +# account_ids = list(string) +# tags = map(string) +# } +# )) +# description = "A map of S3 terraform backends." +# } + +variable "backends" { + type = map(any) +} \ No newline at end of file diff --git a/aws_s3_backend/versions.tf b/aws_s3_backend/versions.tf new file mode 100644 index 0000000..9d3a87c --- /dev/null +++ b/aws_s3_backend/versions.tf @@ -0,0 +1,11 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.45" + configuration_aliases = [aws.management] + } + } +} \ No newline at end of file diff --git a/format_data/locals.tf b/format_data/locals.tf new file mode 100644 index 0000000..10eda5a --- /dev/null +++ b/format_data/locals.tf @@ -0,0 +1,102 @@ +locals { + ## Common + management_account = tomap({ "${var.management_account_name}" = var.management_account }) + accounts = merge(local.management_account, var.accounts) + + list_of_account_providers = flatten([for account_key, account in local.accounts : + [for provider_key, provider in account.shared_settings.providers : { + account = account_key + id = account.shared_settings.id + identity_provider_role_name = format("%s_%s", provider_key, account.shared_settings.id) + identity_provider_name = provider_key + identity_provider_type = lookup(local.providers, provider_key) + inputs = provider + } if provider.enabled] if try(account.shared_settings, null) != null && try(account.shared_settings.providers, null) != null + ]) + + providers = { for name, provider in local.list_of_providers : name => provider.type } + + list_of_providers = var.identity_providers + + + + list_of_ids = distinct([for name, account in local.accounts : account.shared_settings.id if can(account.shared_settings.providers)]) + list_of_provider_roles = distinct([for account_provider in local.list_of_account_providers : { + identity_provider_role_name = account_provider.identity_provider_role_name + identity_provider_type = account_provider.identity_provider_type + identity_provider_name = account_provider.identity_provider_name + }]) + + + ## 1.1. Modules + + /* A list of module types with the names of the accounts in which they were used. */ + list_of_account_modules = flatten([for account_key, account in local.accounts : + [for tf_module in account.modules : { + account = account_key + module = tf_module.name + }] if try(account.modules, null) != null + ]) + + + ## 1.2. shared_settings + + ### 1.2.1. Providers + + /* A list of provider types. */ + list_of_provider_types = distinct([for key, provider in var.identity_providers : provider.type]) + + /* A map of all account names grouped by provider type. */ + list_of_accounts_per_provider = { for provider_type in local.list_of_provider_types : + provider_type => [for tf_module in local.list_of_account_modules : tf_module.account if tf_module.module == provider_type] + } + + ### 'cicd_access_role' + list_of_cicd_role_statements = [for provider in local.list_of_account_providers : { + source_account_names = local.list_of_accounts_per_provider[provider.identity_provider_type] + source_role_name = provider.identity_provider_role_name + destination_account_name = provider.account + statement_sid = replace(provider.identity_provider_name, "/[^0-9a-zA-Z]/", "") + } if length(local.list_of_accounts_per_provider[provider.identity_provider_type]) > 0] + + cicd_role_statements = { for name, account in local.accounts : name => { for statement in local.list_of_cicd_role_statements : statement.statement_sid => { + role_name = statement.source_role_name + account_names = statement.source_account_names + } if statement.destination_account_name == name } } + + ### 'bitbucket_oidc' + bitbucket_oidc_type_name = "bitbucket_oidc" + + bitbucket_oidc_providers = { for i, j in local.list_of_providers : i => { + identity_provider_url = j.identity_provider_url + audience = j.audience + thumbprints = j.thumbprints + } if j.type == local.bitbucket_oidc_type_name } + + bitbucket_oidc_role_names = [for role in local.list_of_provider_roles : role if role.identity_provider_type == local.bitbucket_oidc_type_name] # filtrowanie ról dla modułu 'bitbucket_oidc' + + bitbucket_oidc_role_inputs = [for role in local.bitbucket_oidc_role_names : { + role_name = role.identity_provider_role_name + provider_name = role.identity_provider_name + inputs = [for provider in local.list_of_account_providers : provider.inputs if provider.identity_provider_role_name == role.identity_provider_role_name] + }] + + bitbucket_oidc_roles = { for i in local.bitbucket_oidc_role_inputs : i.role_name => { + provider = i.provider_name + repository_uuids = distinct(flatten([for k in i.inputs : k.repository_uuids])) # k.enabled do usunięcia + } } + + # S3 Backend + backend_list_of_accounts = [for name, account in var.accounts : { # list of accounts with backend enabled + backend_root_name = account.shared_settings.id + account_name = name + create = account.shared_settings.backend.enabled + } if try(account.shared_settings.backend, null) != null] + + backend_list_of_backends = distinct([for account in local.backend_list_of_accounts : account.backend_root_name]) + + backends = { for backend in local.backend_list_of_backends : backend => { + create = true + account_names = flatten([for account in local.backend_list_of_accounts : account.account_name if account.backend_root_name == backend && account.create == true]) + } } +} \ No newline at end of file diff --git a/format_data/outputs.tf b/format_data/outputs.tf new file mode 100644 index 0000000..6064dca --- /dev/null +++ b/format_data/outputs.tf @@ -0,0 +1,58 @@ +#### Identity Providers #### + +### Common ### +output "list_of_account_providers" { + value = local.list_of_account_providers +} + +output "list_of_providers" { + value = local.list_of_providers +} + +output "list_of_provider_types" { + value = local.list_of_provider_types +} + +output "list_of_ids" { + value = local.list_of_ids +} + +output "list_of_provider_roles" { + value = local.list_of_provider_roles +} + +output "list_of_account_modules" { + value = local.list_of_account_modules +} + +output "list_of_accounts_per_provider" { + value = local.list_of_accounts_per_provider +} + +## Roles +output "list_of_cicd_role_statements" { + value = local.list_of_cicd_role_statements +} + +output "cicd_role_statements" { + value = local.cicd_role_statements +} + + +## Modules + +### 'bitbucket_oidc' +output "bitbucket_oidc_providers" { + value = local.bitbucket_oidc_providers +} + +output "bitbucket_oidc_roles" { + value = local.bitbucket_oidc_roles +} + + + +# Test +output "accounts" { + value = local.accounts +} \ No newline at end of file diff --git a/format_data/variables.tf b/format_data/variables.tf new file mode 100644 index 0000000..4eb687a --- /dev/null +++ b/format_data/variables.tf @@ -0,0 +1,83 @@ +variable "accounts" { + type = map(object( + { + email = string + ou = string + + modules = optional(list(object( + { + name = optional(string) + path = optional(string) + } + ))) + + shared_settings = optional(object( + { + id = optional(string) + providers = optional(map(object( + { + enabled = optional(bool) + # bitbucket_oidc + repository_uuids = optional(list(string)) + } + ))) + backend = optional(object( + { + enabled = optional(bool) + } + )) + } + )) + } + )) +} + +variable "management_account" { + type = object( + { + modules = optional(list(object( + { + name = optional(string) + path = optional(string) + } + ))) + + shared_settings = optional(object( + { + id = optional(string) + providers = optional(map(object( + { + enabled = optional(bool) + # bitbucket_oidc + repository_uuids = optional(list(string)) + } + ))) + } + )) + } + ) +} + +variable "management_account_name" { + type = string + description = "A name of the management account." + + validation { + condition = can(regex("^[\\w]{1}([\\w\\s/_-]{0,1}[\\w]{1}){1,31}$", var.management_account_name)) + error_message = "An account name must consist of characters in the ranges [0-9a-zA-Z/_-] and whitespaces. It must start and end with an alphanumeric characters. It cannot contain two consecutive characters in the range [/_-] and whitespaces." + } +} + +variable "identity_providers" { + type = map(object( + { + type = string + # bitbucket_oidc + identity_provider_url = optional(string) + audience = optional(string) + thumbprints = optional(list(string)) + } + )) + description = "" + default = {} +} \ No newline at end of file diff --git a/ses_to_s3_email_forwarder/data.tf b/ses_to_s3_email_forwarder/data.tf new file mode 100644 index 0000000..8fc4b38 --- /dev/null +++ b/ses_to_s3_email_forwarder/data.tf @@ -0,0 +1 @@ +data "aws_caller_identity" "current" {} diff --git a/ses_to_s3_email_forwarder/main.tf b/ses_to_s3_email_forwarder/main.tf new file mode 100644 index 0000000..f007d12 --- /dev/null +++ b/ses_to_s3_email_forwarder/main.tf @@ -0,0 +1,87 @@ +module "s3_bucket" { + providers = { + aws = aws.global + } + source = "terraform-aws-modules/s3-bucket/aws" + version = "3.6.0" + + force_destroy = var.bucket_force_destroy + bucket = var.bucket_name + acl = var.bucket_acl + restrict_public_buckets = var.bucket_restrict_public_buckets + block_public_acls = var.bucket_block_public_acls + block_public_policy = var.bucket_block_public_policy + ignore_public_acls = var.bucket_ignore_public_acls + + versioning = { + enabled = var.bucket_versioning_enabled + } + server_side_encryption_configuration = {} + replication_configuration = {} + + # tags = var.backends[each.key].tags +} + +resource "aws_s3_bucket_policy" "allow_access_from_ses" { + provider = aws.global + + bucket = module.s3_bucket.s3_bucket_id + policy = templatefile("${path.module}/templates/s3_bucket_policy.tftpl", { + s3_bucket_arn = module.s3_bucket.s3_bucket_arn, + aws_referer = data.aws_caller_identity.current.account_id + }) + depends_on = [ + module.s3_bucket + ] +} + +module "ses" { + providers = { + aws = aws.global + } + + source = "clouddrove/ses/aws" + version = "1.0.1" + domain = var.ses_domain + iam_name = var.ses_user + zone_id = var.ses_zone_id + enable_verification = true + enable_mx = true + enable_spf_domain = false + } + +resource "aws_ses_receipt_rule_set" "custom_rule_set" { + provider = aws.global + rule_set_name = "s3-emails-forwarding" +} + +# Add a header to the email and store it in S3 +resource "aws_ses_receipt_rule" "s3_email_forwarding" { + provider = aws.global + name = "store" + rule_set_name = aws_ses_receipt_rule_set.custom_rule_set.id + recipients = ["aws@cholewa.io"] + enabled = true + scan_enabled = true + + add_header_action { + header_name = "Custom-Header" + header_value = "Added by SES" + position = 1 + } + + s3_action { + bucket_name = var.bucket_name + position = 2 + } + + depends_on = [ + aws_ses_receipt_rule_set.custom_rule_set, + module.s3_bucket + ] +} + +resource "aws_ses_active_receipt_rule_set" "main" { + provider = aws.global + rule_set_name = aws_ses_receipt_rule_set.custom_rule_set.id +} diff --git a/ses_to_s3_email_forwarder/outputs.tf b/ses_to_s3_email_forwarder/outputs.tf new file mode 100644 index 0000000..8ec904f --- /dev/null +++ b/ses_to_s3_email_forwarder/outputs.tf @@ -0,0 +1,4 @@ +output bucket_arn { + value = module.s3_bucket.s3_bucket_arn + } + diff --git a/ses_to_s3_email_forwarder/templates/s3_bucket_policy.tftpl b/ses_to_s3_email_forwarder/templates/s3_bucket_policy.tftpl new file mode 100644 index 0000000..103fbfe --- /dev/null +++ b/ses_to_s3_email_forwarder/templates/s3_bucket_policy.tftpl @@ -0,0 +1,19 @@ +{ + "Version":"2012-10-17", + "Statement":[ + { + "Sid":"AllowSESPuts", + "Effect":"Allow", + "Principal":{ + "Service":"ses.amazonaws.com" + }, + "Action":"s3:PutObject", + "Resource":"${s3_bucket_arn}/*", + "Condition":{ + "StringEquals":{ + "aws:Referer":"${aws_referer}" + } + } + } + ] +} diff --git a/ses_to_s3_email_forwarder/variables.tf b/ses_to_s3_email_forwarder/variables.tf new file mode 100644 index 0000000..52c62dd --- /dev/null +++ b/ses_to_s3_email_forwarder/variables.tf @@ -0,0 +1,53 @@ +# S3 Bucket +variable bucket_name { + type = string +} + +variable bucket_force_destroy { + type = bool + default = true +} + +variable bucket_acl { + type = string + default = "private" +} + +variable bucket_restrict_public_buckets { + type = bool + default = true +} + +variable bucket_block_public_acls { + type = bool + default = true +} + +variable bucket_block_public_policy { + type = bool + default = true +} + +variable bucket_ignore_public_acls { + type = bool + default = true +} + +variable bucket_versioning_enabled { + type = bool + default = true +} + +# SES +variable ses_domain { + type = string +} + +variable ses_user { + type = string + default = "ses_user" +} + +variable ses_zone_id { + type = string +} diff --git a/ses_to_s3_email_forwarder/versions.tf b/ses_to_s3_email_forwarder/versions.tf new file mode 100644 index 0000000..4db510e --- /dev/null +++ b/ses_to_s3_email_forwarder/versions.tf @@ -0,0 +1,11 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.45" + configuration_aliases = [ aws.global ] + } + } +} diff --git a/terragrunt_files/locals.tf b/terragrunt_files/locals.tf new file mode 100644 index 0000000..b7f0f8a --- /dev/null +++ b/terragrunt_files/locals.tf @@ -0,0 +1,84 @@ +locals { + accounts = { for name, account in var.accounts : name => { + id = var.account_ids[name] + modules = account.modules + file_name_prefix = "${lower(replace(replace(name, "/", ""), "/[\\s_]+/", "-"))}" + } } + + management_account = tomap({ "${var.management_account_name}" = { + id = var.management_account_id + modules = lookup(var.management_account, "modules", []) + file_name_prefix = "management" + } }) + + accounts_with_management = merge(local.accounts, local.management_account) + + accounts_path = join("", [ + "../../../../", # out of cache if terragrunt enabled + var.accounts_path, + substr(var.accounts_path, -1, -1) != "/" ? "/" : "" # add '/' if it is not at the end of the path + ]) + + warning_msg = "Created by Terraform as a local_file resource. DO NOT modify MANUALLY!" + + env_configs = [for account_name, account in local.accounts : { + name = format("%s_env", account.file_name_prefix) + filename = format("%s/env.hcl", account.file_name_prefix) + content = templatefile(format("%s/templates/env_config.tftpl", path.module), { + warning_msg = local.warning_msg + account_id = account.id + account_name = account_name + }) + }] + + default_module_configs = flatten([for account in local.accounts : [for tf_module in var.default_modules : + { + name = format("%s_%s", account.file_name_prefix, tf_module.name) + filename = format("%s%s/%s/terragrunt.hcl", account.file_name_prefix, length(tf_module.path) > 0 ? format("/%s", tf_module.path) : "", tf_module.name) + content = templatefile(format("%s/templates/module_config.tftpl", path.module), { + warning_msg = local.warning_msg + terragrunt_module_name = tf_module.name + terragrunt_templates_dir = var.templates_path + }) + } + ]]) + + custom_module_configs = flatten([for account_name, account in local.accounts_with_management : [for tf_module in account.modules : + { + name = format("%s_%s", account.file_name_prefix, tf_module.name) # alias_root!!! + filename = format("%s%s/%s/terragrunt.hcl", account.file_name_prefix, length(tf_module.path) > 0 ? format("/%s", tf_module.path) : "", tf_module.name) # alias_root!!! + content = templatefile(format("%s/templates/module_config.tftpl", path.module), { + warning_msg = local.warning_msg + terragrunt_module_name = tf_module.name + terragrunt_templates_dir = var.templates_path + }) + } + ] if try(account.modules, null) != null]) + + configs = { for config in concat(local.env_configs, local.default_module_configs, local.custom_module_configs) : config.name => { + filename = config.filename + content = config.content + } } + +} + + +output "default_module_configs" { + value = local.default_module_configs +} + +output "custom_module_configs" { + value = local.custom_module_configs +} + +output "accounts" { + value = local.accounts +} + +output "management_account" { + value = local.management_account +} + +output "accounts_with_management" { + value = local.accounts_with_management +} \ No newline at end of file diff --git a/terragrunt_files/main.tf b/terragrunt_files/main.tf new file mode 100644 index 0000000..0c81e12 --- /dev/null +++ b/terragrunt_files/main.tf @@ -0,0 +1,7 @@ +resource "local_file" "terragrunt" { + for_each = local.configs + content = each.value["content"] + filename = join("", [local.accounts_path, each.value["filename"]]) + file_permission = "0644" # do zmiennej? + directory_permission = "0755" # do zmiennej? +} \ No newline at end of file diff --git a/terragrunt_files/templates/env_config.tftpl b/terragrunt_files/templates/env_config.tftpl new file mode 100644 index 0000000..8b4799b --- /dev/null +++ b/terragrunt_files/templates/env_config.tftpl @@ -0,0 +1,5 @@ +# ${warning_msg} +locals { + account_id = "${account_id}" + account_name = "${account_name}" +} \ No newline at end of file diff --git a/terragrunt_files/templates/module_config.tftpl b/terragrunt_files/templates/module_config.tftpl new file mode 100644 index 0000000..a7409de --- /dev/null +++ b/terragrunt_files/templates/module_config.tftpl @@ -0,0 +1,10 @@ +# ${warning_msg} +include "config" { + path = find_in_parent_folders("config.hcl") +} + +include "${terragrunt_module_name}" { + path = "$${get_repo_root()}/${terragrunt_templates_dir}/${terragrunt_module_name}.hcl" +} + +# additional custom inputs? \ No newline at end of file diff --git a/terragrunt_files/variables.tf b/terragrunt_files/variables.tf new file mode 100644 index 0000000..c5cf199 --- /dev/null +++ b/terragrunt_files/variables.tf @@ -0,0 +1,103 @@ +variable "accounts" { + type = map(object( + { + modules = optional(list(object( + { + name = string + path = optional(string, "") + } + ))) + } + )) + description = "" + + validation { + condition = alltrue(flatten([for account in var.accounts : [for tf_module in account.modules : can(regex("^([^\\/]{1}.*[^\\/]{1})?$", tf_module.path)) if try(tf_module.path, null) != null] if try(account.modules, null) != null])) + error_message = "A module path cannot start and end with '/' character and must be at least 2 characters long." + } + + validation { + condition = alltrue(flatten([for account in var.accounts : [for tf_module in account.modules : length(tf_module.name) >= 2] if try(account.modules, null) != null])) + error_message = "A module name must be at least 2 characters long." + } + + validation { + condition = alltrue(flatten([for account in var.accounts : length([for tf_module in account.modules : tf_module.name]) == length(distinct([for tf_module in account.modules : tf_module.name])) if try(account.modules, null) != null])) + error_message = "Each module name must be unique." + } +} + +variable "account_ids" { + type = map(string) + description = "" +} + +variable "default_modules" { + type = list(object( + { + name = string + path = optional(string, "") + } + )) + default = [] + description = "" + + validation { + condition = alltrue([for tf_module in var.default_modules : can(regex("^([^\\/]{1}.*[^\\/]{1})?$", tf_module.path)) if try(tf_module.path, null) != null]) + error_message = "A module path cannot start and end with '/' character and must be at least 2 characters long." + } + + validation { + condition = alltrue([for tf_module in var.default_modules : length(tf_module.name) >= 2]) + error_message = "A module name must be at least 2 characters long." + } + + validation { + condition = length([for tf_module in var.default_modules : tf_module.name]) == length(distinct([for tf_module in var.default_modules : tf_module.name])) + error_message = "Each module name must be unique." + } +} + +variable "accounts_path" { + type = string + description = "" + + validation { + condition = can(regex("^([^\\/]{1}.+)?$", var.accounts_path)) + error_message = "An accounts path cannot start with '/' character and must be at least 2 characters long." + } +} + +variable "templates_path" { + type = string + description = "" + + validation { + condition = can(regex("^([^\\/]{1}.+)?$", var.templates_path)) + error_message = "A templates path cannot start with '/' character and must be at least 2 characters long." + } +} + +variable "management_account" { + type = object( + { + modules = optional(list(object( + { + name = string + path = optional(string, "") + } + ))) + } + ) + description = "" +} + +variable "management_account_name" { + type = string + description = "" +} + +variable "management_account_id" { + type = string + description = "" +} \ No newline at end of file