diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4873d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# .tfvars files +*.tfvars + +.terraform/* +logs +common/README.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..85f5ae4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: +- repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.31.0 + hooks: +# - id: terraform_validate + - id: terraform_fmt + - id: terraform_docs_replace + args: ['table'] + exclude: common/*.tf + exclude: version.tf + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.1.0 + hooks: + - id: check-symlinks + - id: detect-aws-credentials + - id: detect-private-key diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e59245c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Versions + +* v1.0.0 -- 20210301 + - initial creation diff --git a/data.tf b/data.tf new file mode 100644 index 0000000..30905b7 --- /dev/null +++ b/data.tf @@ -0,0 +1,23 @@ +data "aws_caller_identity" "current" {} + +data "aws_arn" "current" { + arn = data.aws_caller_identity.current.arn +} + +data "aws_region" "current" {} + +# output "caller_account_id" { +# value = data.aws_caller_identity.current.account_id +# } +# +# output "account_caller_arn" { +# value = data.aws_caller_identity.current.arn +# } +# +# output "account_caller_arn_partition" { +# value = data.aws_arn.current.partition +# } +# +# output "account_region"name" { +# value = data.aws_region.current.name +# } diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..7d873ea --- /dev/null +++ b/locals.tf @@ -0,0 +1,32 @@ +locals { + policies = { + "ced-edde" = [ + aws_iam_policy.policy_app.arn, + ] + } + policies_flat = { for e in keys(local.policies) : e => flatten(local.policies[e]) } + policies_count = { for e in keys(local.policies) : e => length(local.policies_flat[e]) } + + # for automatic ldap creation of group + bocappdata_auth = var.aws_environment == "govcloud" ? "Cloud_AWSGovCloud_Auth" : "Cloud_AWS_Auth" + bocappdata_fullauth = { + "ced-edde" = format("gov.census.tco:%s=%s,%s", local.bocappdata_auth, aws_iam_role.role_app.arn, data.terraform_remote_state.common.outputs.inf_saml_provider) + } + region = var.region + # ldap_init = { for e in keys(local.policies) : lookup(var.ldap_init,e,e) => true } + + ldap_exists = { + "ced-edde" = fileexists("setup/${aws_iam_role.role_app.name}.ldif") + } +} + +#--- +# application stuff +#--- +locals { + app_name = "ced-edde" + role_name = format("r-%v", local.app_name) + policy_name = format("p-%v", local.app_name) + ec2_role_name = format("r-ec2-%v", local.app_name) + ec2_policy_name = format("p-ec2-%v-%v", local.app_name, "transcribe") +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..de83bb7 --- /dev/null +++ b/main.tf @@ -0,0 +1,119 @@ +/* +* # About aws-iam-role +* +* # Usage +* +* ```hcl +* module "myrole" { +* source = "git@github.e.it.census.gov:terraform-modules/aws-iam-role.gi" +* +* role_name = "my-role" +* saml_provider_arn = "aws:aws-us-gov:iam:1234567890:saml/X" +* enable_ldap_creation = true +* assume_policy_document = data.terraform_remote_state.common.outputs.saml_assume_json +* ec2_assume_policy_document = "X" +* attached_policies = ["arn1", "arn2"] +* ldap_user = "cn=myuser,ou=Application,o=U.S. Census Bureau,c=US" +* ldap_password = "password1234$$" +* +* # optional +* ec2_role_name = "my-role-other" +* enable_instance_role = false +* ec2_attached_policies = [] +* ldap_host = "ldap.e.tco.census.gov" +* ldap_port = 389 +* } +* ``` +*/ + +locals { + account_id = var.account_id != "" ? var.account_id : data.aws_caller_identity.current.account_id + region = data.aws_region.current.name + account_environment = data.aws_arn.current.partition == "aws-us-gov" ? "gov" : "ew" + + _ec2_role_name = var.ec2_role_name != "" ? var.ec2_role_name : var.role_name + role_name = format("%v%v", lookup(local._prefixes, "role", ""), var.role_name) + saml_string = var.saml_provider_arn != "" ? "SAML" : "" + role_description = format("%vRole for %v", local.saml_string, var.role_name) + policy_name = format("%v%v", lookup(local._prefixes, "policy", ""), var.role_name) + ec2_role_name = format("%v-ec2-%v", lookup(local._prefixes, "role", ""), local._ec2_role_name) + ec2_policy_name = format("%v-ec2-%v", lookup(local._prefixes, "policy", ""), local._ec2_role_name) + + ldap_exists = fileexists("${path.root}/setup/${aws_iam_role.role_app.name}.ldif") + bocappdata_auth = local.account_environment == "gov" ? "Cloud_AWSGovCloud_Auth" : "Cloud_AWS_Auth" + bocappdata_fullauth = format("gov.census.tco:%v=%v,%v", local.bocappdata_auth, aws_iam_role.role.arn, var.saml_provider_arn) + + enable_ldap = var.enable_ldap_creation && var.ldap_user != "" && var.ldap_password != "" && var.saml_provider_arn != "" + + base_tags = { + "boc:tf_module_version" = local._module_version + "boc:created_by" = "terraform" + } +} + +resource "aws_iam_role" "role" { + name = local.role_name + description = local.role_description + force_detach_policies = false + max_session_duration = 3600 + # assume_role_policy = data.terraform_remote_state.common.outputs.inf_saml_assume_policy_document + assume_role_policy = var.assume_policy_document + + tags = merge( + var.tags, + local.base_tags, + lookup(var.component_tags, "role", {}), + map("Name", local.role_name) + ) +} + +resource "aws_iam_role_policy_attachment" "role" { + for_each = toset(var.attached_policies) + role = aws_iam_role.role.name + policy_arn = each.value +} + +data "template_file" "role" { + template = file("${path.module}/templates/iam-role-ldif.${local.account_environment}.tpl") + vars = { + role_name = aws_iam_role.role.name + role_arn = aws_iam_role.role.arn + account_id = local.account_id + saml_provider_arn = var.saml_provider_arn + aws_environment = local.account_environment + } +} + +resource "null_resource" "role_ldif" { + count = local.enable_ldap ? 1 : 0 + provisioner "local-exec" { + command = "test -d ${path.root}/setup || mkdir ${path.root}/setup" + } + provisioner "local-exec" { + command = "echo '${data.template_file.role.rendered}' > ${path.root}/setup/${aws_iam_role.role.name}.ldif" + } + provisioner "local-exec" { + command = "echo 'Once complete, execute tf-apply again to create LDAP group'" + } +} + +resource "ldap_object" "role" { + count = local.ldap_exists && local.enable_ldap ? 1 : 0 + provider = ldap + dn = format("cn=%s,ou=%s,ou=AWS,ou=Cloud,ou=Application,o=U.S. Census Bureau,c=US", aws_iam_role.role.name, local.account_id) + object_classes = [ + "top", + "bocGroup", + "groupOfNames", + ] + attributes = [ + { description = format("%s account=%s type=%s", aws_iam_role.role.name, local.account_id, local.account_environment) }, + { cn = aws_iam_role.role.name }, + { bocApplicationData = format("gov.census.tco:CPASS_FullPath=Cloud %s %s", local.account_environment, local.account_id) }, + { bocApplicationData = "gov.census.tco:CPASS_APP=CloudServices" }, + { bocApplicationData = local.bocappdata_fullauth }, + ] + lifecycle { + ignore_changes = [object_classes, attributes] + } +} diff --git a/policies.tf b/policies.tf new file mode 100644 index 0000000..726d3b6 --- /dev/null +++ b/policies.tf @@ -0,0 +1,15 @@ +#---- +# STS: ec2 assume +#--- +data "aws_iam_policy_document" "ec2_assume" { + statement { + sid = "AWSEC2AssumeRole" + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} diff --git a/prefixes.tf b/prefixes.tf new file mode 100644 index 0000000..fafcbde --- /dev/null +++ b/prefixes.tf @@ -0,0 +1,12 @@ +locals { + _prefixes = { + "efs" = "v-efs-" + "s3" = "v-s3-" + "ebs" = "v-ebs-" + "kms" = "k-kms-" + "role" = "r-" + "policy" = "p-" + "security-group" = "" + # "security-group" = "sg-" + } +} diff --git a/provider.ldap.tf b/provider.ldap.tf new file mode 100644 index 0000000..a23be2b --- /dev/null +++ b/provider.ldap.tf @@ -0,0 +1,7 @@ +provider "ldap" { + ldap_host = var.ldap_host + ldap_port = var.ldap_port + use_tls = true + bind_user = var.ldap_user + bind_password = var.ldap_password +} diff --git a/templates/iam-role-ldif.east-west.tpl b/templates/iam-role-ldif.east-west.tpl new file mode 100644 index 0000000..eaa395e --- /dev/null +++ b/templates/iam-role-ldif.east-west.tpl @@ -0,0 +1,13 @@ +# fields: role_name role_arn account_id saml_provider_arn aws_environment + +# ${role_name}, ${account_id}, AWS, Cloud, Application, U.S. Census Bureau, US +dn: cn=${role_name},ou=${account_id},ou=AWS,ou=Cloud,ou=Application,o=U.S. Census Bureau,c=US +description: ${role_name} ( ${account_id} ) +cn: ${role_name} +bocApplicationData: gov.census.tco:CPASS_FullPath=Cloud ${aws_environment} ${account_id} +bocApplicationData: gov.census.tco:CPASS_APP=CloudServices +bocApplicationData: gov.census.tco:Cloud_AWS_Auth=${role_arn},${saml_provider_arn} +#bocApplicationData: gov.census.tco:Cloud_AWSGovCloud_Auth=${role_arn},${saml_provider_arn} +objectClass: groupOfNames +objectClass: bocGroup +objectClass: Top diff --git a/templates/iam-role-ldif.ew.tpl b/templates/iam-role-ldif.ew.tpl new file mode 120000 index 0000000..65f8fca --- /dev/null +++ b/templates/iam-role-ldif.ew.tpl @@ -0,0 +1 @@ +iam-role-ldif.east-west.tpl \ No newline at end of file diff --git a/templates/iam-role-ldif.gov.tpl b/templates/iam-role-ldif.gov.tpl new file mode 120000 index 0000000..753ee41 --- /dev/null +++ b/templates/iam-role-ldif.gov.tpl @@ -0,0 +1 @@ +iam-role-ldif.govcloud.tpl \ No newline at end of file diff --git a/templates/iam-role-ldif.govcloud.tpl b/templates/iam-role-ldif.govcloud.tpl new file mode 100644 index 0000000..7499c8d --- /dev/null +++ b/templates/iam-role-ldif.govcloud.tpl @@ -0,0 +1,13 @@ +# fields: role_name role_arn account_id saml_provider_arn aws_environment + +# ${role_name}, ${account_id}, AWS, Cloud, Application, U.S. Census Bureau, US +dn: cn=${role_name},ou=${account_id},ou=AWS,ou=Cloud,ou=Application,o=U.S. Census Bureau,c=US +description: ${role_name} ( ${account_id} ) +cn: ${role_name} +bocApplicationData: gov.census.tco:CPASS_FullPath=Cloud ${aws_environment} ${account_id} +bocApplicationData: gov.census.tco:CPASS_APP=CloudServices +#bocApplicationData: gov.census.tco:Cloud_AWS_Auth=${role_arn},${saml_provider_arn} +bocApplicationData: gov.census.tco:Cloud_AWSGovCloud_Auth=${role_arn},${saml_provider_arn} +objectClass: groupOfNames +objectClass: bocGroup +objectClass: Top diff --git a/variables.common.tf b/variables.common.tf new file mode 100644 index 0000000..2f1a864 --- /dev/null +++ b/variables.common.tf @@ -0,0 +1,26 @@ +#--- +# account info +#--- +variable "account_id" { + description = "AWS Account ID (default will pull from current user)" + type = string + default = "" +} + +variable "account_alias" { + description = "AWS Account Alias" + type = string + default = "" +} + +variable "override_prefixes" { + description = "Override built-in prefixes by component (role, policy). This should be used primarily for common infrastructure things" + type = map(string) + default = {} +} + +variable "tags" { + description = "AWS Tags to apply to appropriate resources (S3, KMS). Do not include safeguard tags here, use the data_safeguard field for such things." + type = map(string) + default = {} +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..5f5dae8 --- /dev/null +++ b/variables.tf @@ -0,0 +1,90 @@ +#--- +# application stuff +#--- +#locals { +# app_name = "ced-edde" +# role_name = format("r-%v", local.app_name) +# policy_name = format("p-%v", local.app_name) +# ec2_role_name = format("r-ec2-%v", local.app_name) +# ec2_policy_name = format("p-ec2-%v-%v", local.app_name, "transcribe") +#} + +variable "role_name" { + description = "Role/application name without prefix" + type = string +} + +variable "ec2_role_name" { + description = "EC2 instace Role/application name without prefix" + type = string + default = "" +} + +variable "saml_provider_arn" { + description = "ARN of SAML Provider" + type = string + default = "" +} + +variable "enable_ldap_creation" { + description = "Flag to enable/disable LDAP object creation for role group (for SAML only). Also requires LDAP credentials." + type = boolean + default = false +} + +variable "enable_instance_role" { + description = "Flag to enable the creation of a partner EC2 instance role with specific policies and optionally a different name" + type = boolean + default = false +} + +variable "assume_policy_document" { + description = "JSON policy document for role to assume (i.e., the SAML assume document)" + type = string + default = "" +} + +variable "ec2_assume_policy_document" { + description = "JSON policy document for EC2 instance role (default is sts:AssumeRole for ec2 service)" + type = string + default = "" +} + +variable "attached_policies" { + description = "List of IAM Policy ARNs to attach to this role" + type = list(string) + default = [] +} + +variable "ec2_attached_policies" { + description = "List of IAM Policy ARNs to attach to this EC2 instance role" + type = list(string) + default = [] +} + +#--- +# ldap +#--- +variable "ldap_user" { + description = "LDAP user for writing data into eDirectory or Active Directory" + type = string + default = "" +} + +variable "ldap_password" { + description = "LDAP password for ldap_user for writing data into eDirectory or Active Directory" + type = string + default = "" +} + +variable "ldap_host" { + description = "LDAP Hostname (default is for eBOCAS)" + type = string + default = "ldap.e.tco.census.gov" +} + +variable "ldap_port" { + description = "LDAP port (default is 389 but also using STARTTLS)" + type = number + default = 389 +} diff --git a/version.tf b/version.tf new file mode 100644 index 0000000..fa2705b --- /dev/null +++ b/version.tf @@ -0,0 +1,3 @@ +locals { + _module_version = "1.0.0" +}