diff --git a/examples/customer-managed-policies/.header.md b/examples/customer-managed-policies/.header.md
new file mode 100644
index 0000000..060e460
--- /dev/null
+++ b/examples/customer-managed-policies/.header.md
@@ -0,0 +1,40 @@
+This directory contains examples of using the module to create users and groups and assign permissions with **Inline Policies**.
+
+**IMPORTANT:** Ensure that the name of your object matches the name of your principal (e.g. user name or group name). See the following example with object/principal names 'Admin' and 'nuzumaki':
+
+```hcl
+ sso_groups = {
+ Admin : {
+ group_name = "Admin"
+ group_description = "Admin IAM Identity Center Group"
+ },
+ }
+
+ // Create desired USERS in IAM Identity Center
+ sso_users = {
+ nuzumaki : {
+ group_membership = ["Admin",]
+ user_name = "nuzumaki"
+ given_name = "Naruto"
+ family_name = "Uzumaki"
+ email = "nuzumaki@hiddenleaf.village"
+ },
+ }
+
+```
+
+These names are referenced throughout the module. Failure to do this may lead to unintentional errors such as the following:
+
+```
+Error: Invalid index
+│
+│ on ../../main.tf line 141, in resource "aws_identitystore_group_membership" "sso_group_membership":
+│ 141: member_id = (contains(local.this_users, each.value.user_name) ? aws_identitystore_user.sso_users[each.value.user_name].user_id : data.aws_identitystore_user.existing_sso_users[each.value.user_name].id)
+│ ├────────────────
+│ │ aws_identitystore_user.sso_users is object with 2 attributes
+│ │ each.value.user_name is "nuzumaki"
+│
+│ The given key does not identify an element in this collection value.
+```
+
+To resolve this, ensure your object and principal names are the same and re-run `terraform plan` and `terraform apply`.
diff --git a/examples/customer-managed-policies/README.md b/examples/customer-managed-policies/README.md
new file mode 100644
index 0000000..8ff95ca
--- /dev/null
+++ b/examples/customer-managed-policies/README.md
@@ -0,0 +1,72 @@
+
+This directory contains examples of using the module to create users and groups and assign permissions with **Inline Policies**.
+
+**IMPORTANT:** Ensure that the name of your object matches the name of your principal (e.g. user name or group name). See the following example with object/principal names 'Admin' and 'nuzumaki':
+
+```hcl
+ sso_groups = {
+ Admin : {
+ group_name = "Admin"
+ group_description = "Admin IAM Identity Center Group"
+ },
+ }
+
+ // Create desired USERS in IAM Identity Center
+ sso_users = {
+ nuzumaki : {
+ group_membership = ["Admin",]
+ user_name = "nuzumaki"
+ given_name = "Naruto"
+ family_name = "Uzumaki"
+ email = "nuzumaki@hiddenleaf.village"
+ },
+ }
+
+```
+
+These names are referenced throughout the module. Failure to do this may lead to unintentional errors such as the following:
+
+```
+Error: Invalid index
+│
+│ on ../../main.tf line 141, in resource "aws_identitystore_group_membership" "sso_group_membership":
+│ 141: member_id = (contains(local.this_users, each.value.user_name) ? aws_identitystore_user.sso_users[each.value.user_name].user_id : data.aws_identitystore_user.existing_sso_users[each.value.user_name].id)
+│ ├────────────────
+│ │ aws_identitystore_user.sso_users is object with 2 attributes
+│ │ each.value.user_name is "nuzumaki"
+│
+│ The given key does not identify an element in this collection value.
+```
+
+To resolve this, ensure your object and principal names are the same and re-run `terraform plan` and `terraform apply`.
+
+## Requirements
+
+No requirements.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | n/a |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [aws-iam-identity-center](#module\_aws-iam-identity-center) | ../.. | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_organizations_organization.org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organization) | data source |
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+No outputs.
+
\ No newline at end of file
diff --git a/examples/customer-managed-policies/locals.tf b/examples/customer-managed-policies/locals.tf
new file mode 100644
index 0000000..922f5e9
--- /dev/null
+++ b/examples/customer-managed-policies/locals.tf
@@ -0,0 +1,14 @@
+# Fetch Account Id from SSM Parameter Store
+data "aws_ssm_parameter" "account1_account_id" {
+ name = "tf-aws-iam-idc-module-testing-account1-account-id" // replace with your SSM Parameter Key
+}
+
+locals {
+ # Account IDs
+ account1_account_id = nonsensitive(data.aws_ssm_parameter.account1_account_id.value)
+ # account1_account_id = "111111111111"
+ # account2_account_id = "222222222222"
+ # account3_account_id = "333333333333"
+ # account4_account_id = "444444444444"
+
+}
diff --git a/examples/customer-managed-policies/main.tf b/examples/customer-managed-policies/main.tf
new file mode 100644
index 0000000..49802ab
--- /dev/null
+++ b/examples/customer-managed-policies/main.tf
@@ -0,0 +1,112 @@
+data "aws_organizations_organization" "org" {}
+
+module "aws-iam-identity-center" {
+ source = "../.." // local example
+ # source = "aws-ia/iam-identity-center/aws" // remote example
+
+ existing_sso_groups = {
+ AWSControlTowerAdmins : {
+ group_name = "AWSControlTowerAdmins" # this must be the name of a sso group that already exists in your AWS account
+ }
+ }
+
+ sso_groups = {
+ Admin : {
+ group_name = "Admin"
+ group_description = "Admin Group"
+ },
+ Dev : {
+ group_name = "Dev"
+ group_description = "Dev Group"
+ },
+ }
+ sso_users = {
+ nuzumaki : {
+ group_membership = ["Admin", "Dev", "AWSControlTowerAdmins"]
+ user_name = "nuzumaki"
+ given_name = "Naruto"
+ family_name = "Uzumaki"
+ email = "nuzumaki@hiddenleaf.village"
+ },
+ suchiha : {
+ group_membership = ["Dev", "AWSControlTowerAdmins"]
+ user_name = "suchiha"
+ given_name = "Sasuke"
+ family_name = "Uchiha"
+ email = "suchiha@hiddenleaf.village"
+ },
+ }
+
+ existing_permission_sets = {
+ AWSAdministratorAccess : {
+ permission_set_name = "AWSAdministratorAccess" # this must be the name of a permission set that already exists in your AWS account
+ },
+ }
+
+ permission_sets = {
+ AdministratorAccess = {
+ description = "Provides full access to AWS services and resources",
+ session_duration = "PT3H",
+ aws_managed_policies = ["arn:aws:iam::aws:policy/AdministratorAccess"]
+
+ customer_managed_policies = [
+ "MyExampleOrgAdminAccess",
+ ]
+
+ tags = { ManagedBy = "Terraform" }
+ },
+ ViewOnlyAccess = {
+ description = "This policy grants permissions to view resources and basic metadata across all AWS services",
+ session_duration = "PT3H",
+ aws_managed_policies = ["arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"]
+ managed_policy_arn = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
+
+ customer_managed_policies = [
+ {
+ name = "MyExampleOrgViewOnlyAccess"
+ path = "/foo/example/"
+ }
+ ]
+
+ permissions_boundary = {
+ managed_policy_arn = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
+ }
+ tags = { ManagedBy = "Terraform" }
+ },
+ }
+ account_assignments = {
+ Admin : {
+ principal_name = "Admin"
+ principal_type = "GROUP"
+ principal_idp = "INTERNAL"
+ permission_sets = [
+ "AdministratorAccess",
+ "ViewOnlyAccess",
+ // existing permission set
+ "AWSAdministratorAccess",
+ ]
+ account_ids = [
+ // replace with your own account id
+ local.account1_account_id,
+ # local.account2_account_id
+ # local.account3_account_id
+ # local.account4_account_id
+ ]
+ },
+ Dev : {
+ principal_name = "Dev"
+ principal_type = "GROUP"
+ principal_idp = "INTERNAL"
+ permission_sets = [
+ "ViewOnlyAccess",
+ ]
+ account_ids = [
+ // replace with your own account id
+ local.account1_account_id,
+ # local.account2_account_id
+ # local.account3_account_id
+ # local.account4_account_id
+ ]
+ },
+ }
+}
diff --git a/locals.tf b/locals.tf
index d996c3d..0b5575c 100644
--- a/locals.tf
+++ b/locals.tf
@@ -78,8 +78,8 @@ locals {
for pset_name, pset_index in local.customer_managed_permission_sets : [
for policy in pset_index.customer_managed_policies : {
pset_name = pset_name
- policy_name = policy
- # path = path
+ policy_name = try(policy.name, policy)
+ policy_path = try(policy.path, "/")
} if pset_index.customer_managed_policies != null && can(pset_index.customer_managed_policies)
]
])
@@ -207,7 +207,7 @@ locals {
])
# Creating a local variable by flattening the complex type related to Applications to extract a simple structure representing
- # app assignments access scopes
+ # app assignments access scopes
apps_assignments_access_scopes = flatten([
for app in var.sso_applications : [
for ass_acc_scope in app.assignments_access_scope : {
diff --git a/main.tf b/main.tf
index 9fa2617..e88611a 100644
--- a/main.tf
+++ b/main.tf
@@ -191,7 +191,7 @@ resource "aws_ssoadmin_customer_managed_policy_attachment" "pset_customer_manage
permission_set_arn = aws_ssoadmin_permission_set.pset[each.value.pset_name].arn
customer_managed_policy_reference {
name = each.value.policy_name
- path = "/"
+ path = each.value.policy_path
}
}
@@ -271,7 +271,7 @@ resource "aws_ssoadmin_application" "sso_apps" {
tags = each.value.tags
}
-# SSO - Applications Assigments Configuration
+# SSO - Applications Assigments Configuration
resource "aws_ssoadmin_application_assignment_configuration" "sso_apps_assignments_configs" {
for_each = {
for idx, assignment_config in local.apps_assignments_configs :
@@ -281,7 +281,7 @@ resource "aws_ssoadmin_application_assignment_configuration" "sso_apps_assignmen
assignment_required = each.value.assignment_required
}
-# SSO - Application Assignments access scope
+# SSO - Application Assignments access scope
resource "aws_ssoadmin_application_access_scope" "sso_apps_assignments_access_scope" {
for_each = {
for idx, app_access_scope in local.apps_assignments_access_scopes :
@@ -289,20 +289,20 @@ resource "aws_ssoadmin_application_access_scope" "sso_apps_assignments_access_sc
}
application_arn = aws_ssoadmin_application.sso_apps[each.value.app_name].application_arn
authorized_targets = [
- for target in each.value.authorized_targets : aws_ssoadmin_application.sso_apps[target].application_arn
+ for target in each.value.authorized_targets : aws_ssoadmin_application.sso_apps[target].application_arn
]
#authorized_targets = each.value.authorized_targets
scope = each.value.scope
}
-# SSO - Applications Assignments
+# SSO - Applications Assignments
# Groups assignments
resource "aws_ssoadmin_application_assignment" "sso_apps_groups_assignments" {
for_each = {
for idx, assignment in local.apps_groups_assignments :
"${assignment.app_name}-${assignment.group_name}" => assignment
}
- application_arn = aws_ssoadmin_application.sso_apps[each.value.app_name].application_arn
+ application_arn = aws_ssoadmin_application.sso_apps[each.value.app_name].application_arn
principal_id = (contains(local.this_groups, each.value.group_name) ? aws_identitystore_group.sso_groups[each.value.group_name].group_id : data.aws_identitystore_group.existing_sso_groups[each.value.group_name].group_id)
principal_type = each.value.principal_type
}
@@ -313,22 +313,22 @@ resource "aws_ssoadmin_application_assignment" "sso_apps_users_assignments" {
for idx, assignment in local.apps_users_assignments :
"${assignment.app_name}-${assignment.user_name}" => assignment
}
- application_arn = aws_ssoadmin_application.sso_apps[each.value.app_name].application_arn
+ application_arn = aws_ssoadmin_application.sso_apps[each.value.app_name].application_arn
principal_id = (contains(local.this_users, each.value.user_name) ? aws_identitystore_user.sso_users[each.value.user_name].user_id : data.aws_identitystore_user.existing_sso_users[each.value.user_name].user_id)
principal_type = each.value.principal_type
}
# SSO Instance Access Control Attributes
-resource "aws_ssoadmin_instance_access_control_attributes" "sso_access_control_attributes" {
- count = length(var.sso_instance_access_control_attributes) <= 0 ? 0 : 1
+resource "aws_ssoadmin_instance_access_control_attributes" "sso_access_control_attributes" {
+ count = length(var.sso_instance_access_control_attributes) <= 0 ? 0 : 1
instance_arn = local.ssoadmin_instance_arn
dynamic "attribute" {
for_each = var.sso_instance_access_control_attributes
content {
- key = attribute.key
+ key = attribute.key
value {
source = attribute.value.source
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/08_customer_managed_policies.tftest.hcl b/tests/08_customer_managed_policies.tftest.hcl
new file mode 100644
index 0000000..5b55cf4
--- /dev/null
+++ b/tests/08_customer_managed_policies.tftest.hcl
@@ -0,0 +1,13 @@
+run "unit_test" {
+ command = plan
+ module {
+ source = "./examples/customer-managed-policies"
+ }
+}
+
+run "e2e_test" {
+ command = apply
+ module {
+ source = "./examples/customer-managed-policies"
+ }
+}
diff --git a/variables.tf b/variables.tf
index 5fa4a95..662651d 100644
--- a/variables.tf
+++ b/variables.tf
@@ -151,7 +151,7 @@ variable "sso_instance_access_control_attributes" {
description = "List of attributes for access control. This is used to create the enable and use attributes for access control."
type = list(object({
attribute_name = string
- source = set(string)
+ source = set(string)
}))
default = []
validation {
@@ -166,7 +166,7 @@ variable "sso_instance_access_control_attributes" {
condition = alltrue([
for attr in var.sso_instance_access_control_attributes :
attr.source != null &&
- length(attr.source) > 0 && # checks if the set is not empty
+ length(attr.source) > 0 && # checks if the set is not empty
alltrue([for s in attr.source : s != ""]) # checks no empty strings in set
])
error_message = "The attribute source is mandatory and must contain non-empty strings."