From 92d4e7635a605ea099c6415d7e45ebdb0c70447b Mon Sep 17 00:00:00 2001 From: reste85 Date: Mon, 25 Nov 2024 10:12:04 +0100 Subject: [PATCH 1/6] add support for: IAM IdC Applications (with testing) --- .header.md | 56 ++++++++ README.md | 66 ++++++++- data.tf | 1 - .../create-apps-and-assignments/.header.md | 66 +++++++++ .../create-apps-and-assignments/locals.tf | 14 ++ examples/create-apps-and-assignments/main.tf | 128 ++++++++++++++++++ .../.header.md | 66 +++++++++ .../locals.tf | 14 ++ .../existing-users-groups-create-apps/main.tf | 113 ++++++++++++++++ locals.tf | 53 ++++++++ main.tf | 70 ++++++++++ outputs.tf | 15 ++ .../05_create_apps_and_assignments.tftest.hcl | 13 ++ ...6_existing_user_groups_and_apps.tftest.hcl | 13 ++ variables.tf | 38 ++++++ 15 files changed, 724 insertions(+), 2 deletions(-) create mode 100644 examples/create-apps-and-assignments/.header.md create mode 100644 examples/create-apps-and-assignments/locals.tf create mode 100644 examples/create-apps-and-assignments/main.tf create mode 100644 examples/existing-users-groups-create-apps/.header.md create mode 100644 examples/existing-users-groups-create-apps/locals.tf create mode 100644 examples/existing-users-groups-create-apps/main.tf create mode 100644 tests/05_create_apps_and_assignments.tftest.hcl create mode 100644 tests/06_existing_user_groups_and_apps.tftest.hcl diff --git a/.header.md b/.header.md index 42d1ce6..8333fc7 100644 --- a/.header.md +++ b/.header.md @@ -11,6 +11,8 @@ - Dynamic Reference of Existing Groups - AWS Managed Policy Support - Customer Managed Policy Support +- Dynamic Application Creation (with Portal Options, Users and Groups assignments and Assignments Access Scopes configuration) + ## Important @@ -37,6 +39,32 @@ }, } + // Create desired Applications in IAM Identity Center + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + } + } + ``` The object/principal names are referenced throughout the module. Failure to follow this guidance may lead to unintentional errors such as the following: @@ -167,6 +195,34 @@ module "aws-iam-identity-center" { } ``` +## Basic Usage - Create Applications and assign to Users and Groups +``` + // Create desired Applications in IAM Identity Center + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + } + } +``` ## Contributing See the `CONTRIBUTING.md` file for information on how to contribute. diff --git a/README.md b/README.md index 6cf8d50..8415b5b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - Dynamic Reference of Existing Groups - AWS Managed Policy Support - Customer Managed Policy Support +- Dynamic Application Creation (with Portal Options, Users and Groups assignments and Assignments Access Scopes configuration) ## Important @@ -38,6 +39,32 @@ }, } + // Create desired Applications in IAM Identity Center + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + } + } + ``` The object/principal names are referenced throughout the module. Failure to follow this guidance may lead to unintentional errors such as the following: @@ -168,6 +195,34 @@ module "aws-iam-identity-center" { } ``` +## Basic Usage - Create Applications and assign to Users and Groups +``` + // Create desired Applications in IAM Identity Center + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + } + } +``` ## Contributing See the `CONTRIBUTING.md` file for information on how to contribute. @@ -184,7 +239,7 @@ See the `CONTRIBUTING.md` file for information on how to contribute. | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.35.0 | +| [aws](#provider\_aws) | 5.73.0 | ## Modules @@ -199,6 +254,11 @@ No modules. | [aws_identitystore_group_membership.sso_group_membership_existing_google_sso_users](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/identitystore_group_membership) | resource | | [aws_identitystore_user.sso_users](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/identitystore_user) | resource | | [aws_ssoadmin_account_assignment.account_assignment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_account_assignment) | resource | +| [aws_ssoadmin_application.sso_apps](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_application) | resource | +| [aws_ssoadmin_application_access_scope.sso_apps_assignments_access_scope](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_application_access_scope) | resource | +| [aws_ssoadmin_application_assignment.sso_apps_groups_assignments](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_application_assignment) | resource | +| [aws_ssoadmin_application_assignment.sso_apps_users_assignments](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_application_assignment) | resource | +| [aws_ssoadmin_application_assignment_configuration.sso_apps_assignments_configs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_application_assignment_configuration) | resource | | [aws_ssoadmin_customer_managed_policy_attachment.pset_customer_managed_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_customer_managed_policy_attachment) | resource | | [aws_ssoadmin_managed_policy_attachment.pset_aws_managed_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_managed_policy_attachment) | resource | | [aws_ssoadmin_permission_set.pset](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_permission_set) | resource | @@ -222,6 +282,7 @@ No modules. | [existing\_sso\_groups](#input\_existing\_sso\_groups) | Names of the existing groups that you wish to reference from IAM Identity Center. |
map(object({
group_name = string
}))
| `{}` | no | | [existing\_sso\_users](#input\_existing\_sso\_users) | Names of the existing users that you wish to reference from IAM Identity Center. |
map(object({
user_name = string
group_membership = optional(list(string), null) // only used if your IdP only syncs users, and you wish to manage which groups they should go in
}))
| `{}` | no | | [permission\_sets](#input\_permission\_sets) | Permission Sets that you wish to create in IAM Identity Center. This variable is a map of maps containing Permission Set names as keys. See permission\_sets description in README for information about map values. | `any` | `{}` | no | +| [sso\_applications](#input\_sso\_applications) | List of applications to be created in IAM Identity Center |
map(object({
name = string
application_provider_arn = string
description = optional(string)
portal_options = optional(object({
sign_in_options = optional(object({
application_url = optional(string)
origin = string
}))
visibility = optional(string)
}))
status = string # acceptable values are "ENABLED" or "DISABLED"
client_token = optional(string)
tags = optional(map(string))
assignment_required = bool # Resource: aws_ssoadmin_application_assignment_configuration
assignments_access_scope = optional(
list(object({
authorized_targets = optional(list(string)) # List of application names
scope = string
}))
) # Resource: aws_ssoadmin_application_access_scope
group_assignments = optional(list(string)) # Resource aws_ssoadmin_application_assignment, keeping it separated for groups
user_assignments = optional(list(string)) # Resource aws_ssoadmin_application_assignment, keeping it separated for users
}))
| `{}` | no | | [sso\_groups](#input\_sso\_groups) | Names of the groups you wish to create in IAM Identity Center. |
map(object({
group_name = string
group_description = optional(string, null)
}))
| `{}` | no | | [sso\_users](#input\_sso\_users) | Names of the users you wish to create in IAM Identity Center. |
map(object({
display_name = optional(string)
user_name = string
group_membership = list(string)
# Name
given_name = string
middle_name = optional(string, null)
family_name = string
name_formatted = optional(string)
honorific_prefix = optional(string, null)
honorific_suffix = optional(string, null)
# Email
email = string
email_type = optional(string, null)
is_primary_email = optional(bool, true)
# Phone Number
phone_number = optional(string, null)
phone_number_type = optional(string, null)
is_primary_phone_number = optional(bool, true)
# Address
country = optional(string, " ")
locality = optional(string, " ")
address_formatted = optional(string)
postal_code = optional(string, " ")
is_primary_address = optional(bool, true)
region = optional(string, " ")
street_address = optional(string, " ")
address_type = optional(string, null)
# Additional
user_type = optional(string, null)
title = optional(string, null)
locale = optional(string, null)
nickname = optional(string, null)
preferred_language = optional(string, null)
profile_url = optional(string, null)
timezone = optional(string, null)
}))
| `{}` | no | @@ -231,5 +292,8 @@ No modules. |------|-------------| | [account\_assignment\_data](#output\_account\_assignment\_data) | Tuple containing account assignment data | | [principals\_and\_assignments](#output\_principals\_and\_assignments) | Map containing account assignment data | +| [sso\_applications\_arns](#output\_sso\_applications\_arns) | A map of SSO Applications ARNs created by this module | +| [sso\_applications\_group\_assignments](#output\_sso\_applications\_group\_assignments) | A map of SSO Applications assignments with groups created by this module | +| [sso\_applications\_user\_assignments](#output\_sso\_applications\_user\_assignments) | A map of SSO Applications assignments with users created by this module | | [sso\_groups\_ids](#output\_sso\_groups\_ids) | A map of SSO groups ids created by this module | \ No newline at end of file diff --git a/data.tf b/data.tf index 48976ab..0ae661a 100644 --- a/data.tf +++ b/data.tf @@ -98,4 +98,3 @@ data "aws_ssoadmin_permission_set" "existing_permission_sets" { # permission_sets = "ViewOnlyAccess" # account_ids = "111111111111" # } - diff --git a/examples/create-apps-and-assignments/.header.md b/examples/create-apps-and-assignments/.header.md new file mode 100644 index 0000000..140d38b --- /dev/null +++ b/examples/create-apps-and-assignments/.header.md @@ -0,0 +1,66 @@ +This directory contains examples of using the module to **create** applications, application assignments configurations, users, groups and application assignments to both users and groups + +**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" + }, + } + + // Create desired Applications in IAM Identity Center + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + } + } + +``` + +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/create-apps-and-assignments/locals.tf b/examples/create-apps-and-assignments/locals.tf new file mode 100644 index 0000000..922f5e9 --- /dev/null +++ b/examples/create-apps-and-assignments/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/create-apps-and-assignments/main.tf b/examples/create-apps-and-assignments/main.tf new file mode 100644 index 0000000..ad0e8ed --- /dev/null +++ b/examples/create-apps-and-assignments/main.tf @@ -0,0 +1,128 @@ +module "aws-iam-identity-center" { + source = "../.." // local example + # source = "aws-ia/iam-identity-center/aws" // remote example + + // Create desired GROUPS in IAM Identity Center + sso_groups = { + Admin : { + group_name = "Admin" + group_description = "Admin IAM Identity Center Group" + }, + Dev : { + group_name = "Dev" + group_description = "Dev 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" + }, + suchiha : { + group_membership = ["Dev"] + user_name = "suchiha" + given_name = "Sasuke" + family_name = "Uchiha" + email = "suchiha@hiddenleaf.village" + }, + } + + // Create permissions sets backed by AWS managed policies + permission_sets = { + AdministratorAccess = { + description = "Provides AWS full access permissions.", + session_duration = "PT4H", // how long until session expires - this means 4 hours. max is 12 hours + aws_managed_policies = ["arn:aws:iam::aws:policy/AdministratorAccess"] + tags = { ManagedBy = "Terraform" } + }, + ViewOnlyAccess = { + description = "Provides AWS view only permissions.", + session_duration = "PT3H", // how long until session expires - this means 3 hours. max is 12 hours + aws_managed_policies = ["arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"] + tags = { ManagedBy = "Terraform" } + }, + } + + // Assign users/groups access to accounts with the specified permissions + account_assignments = { + Admin : { + principal_name = "Admin" # name of the user or group you wish to have access to the account(s) + principal_type = "GROUP" # entity type (user or group) you wish to have access to the account(s). Valid values are "USER" or "GROUP" + principal_idp = "INTERNAL" # type of Identity Provider you are using. Valid values are "INTERNAL" (using Identity Store) or "EXTERNAL" (using external IdP such as EntraID, Okta, Google, etc.) + permission_sets = ["AdministratorAccess", "ViewOnlyAccess"] // permissions the user/group will have in the account(s) + account_ids = [ // account(s) the group will have access to. Permissions they will have in account are above line + local.account1_account_id, + # local.account2_account_id, + # local.account3_account_id, // these are defined in a locals.tf file, example is in this directory + # local.account4_account_id, + ] + }, + Dev : { + principal_name = "Dev" + principal_type = "GROUP" + principal_idp = "INTERNAL" # type of Identity Provider you are using. Valid values are "INTERNAL" (using Identity Store) or "EXTERNAL" (using external IdP such as EntraID, Okta, Google, etc.) + permission_sets = ["ViewOnlyAccess"] + account_ids = [ + local.account1_account_id, + # local.account2_account_id, + # local.account3_account_id, + # local.account4_account_id, + ] + }, + } + + // Applications + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + tags = { ManagedBy = "Terraform" } + }, + SecondApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the Second Application" + name = "SecondApplication" + portal_options = { + sign_in_options = { + application_url = "http://example2.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication", "SecondApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Admin"] + user_assignments = ["suchiha"] + } + } + +} diff --git a/examples/existing-users-groups-create-apps/.header.md b/examples/existing-users-groups-create-apps/.header.md new file mode 100644 index 0000000..992cd80 --- /dev/null +++ b/examples/existing-users-groups-create-apps/.header.md @@ -0,0 +1,66 @@ +This directory contains examples of using the module to **reference existing** users and groups and assign permissions with **AWS Managed Policies** and assign SSO Applications to users and groups + +**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" + }, + } + + // Create desired Applications in IAM Identity Center + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + } + } + +``` + +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/existing-users-groups-create-apps/locals.tf b/examples/existing-users-groups-create-apps/locals.tf new file mode 100644 index 0000000..922f5e9 --- /dev/null +++ b/examples/existing-users-groups-create-apps/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/existing-users-groups-create-apps/main.tf b/examples/existing-users-groups-create-apps/main.tf new file mode 100644 index 0000000..db2e324 --- /dev/null +++ b/examples/existing-users-groups-create-apps/main.tf @@ -0,0 +1,113 @@ +module "aws-iam-identity-center" { + source = "../.." // local example + # source = "aws-ia/iam-identity-center/aws" // remote example + + # Ensure these User/Groups already exist in your AWS account + existing_sso_groups = { + testgroup : { + group_name = "testgroup" # this must be the name of a group that already exists in your AWS account + }, + } + existing_sso_users = { + testuser : { + user_name = "testuser" # this must be the name of a user that already exists in your AWS account + }, + } + + + # Create permissions sets backed by AWS managed policies + permission_sets = { + AdministratorAccess = { + description = "Provides AWS full access permissions.", + session_duration = "PT4H", // how long until session expires - this means 4 hours. max is 12 hours + aws_managed_policies = ["arn:aws:iam::aws:policy/AdministratorAccess"] + tags = { ManagedBy = "Terraform" } + }, + ViewOnlyAccess = { + description = "Provides AWS view only permissions.", + session_duration = "PT3H", // how long until session expires - this means 3 hours. max is 12 hours + aws_managed_policies = ["arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"] + tags = { ManagedBy = "Terraform" } + }, + } + + + # Assign users/groups access to accounts with the specified permissions + # Ensure these User/Groups already exist in your AWS account + account_assignments = { + testgroup : { + principal_name = "testgroup" + principal_type = "GROUP" + principal_idp = "EXTERNAL" + permission_sets = ["AdministratorAccess", "ViewOnlyAccess", ] + account_ids = [ // account(s) the user will have access to. Permissions they will have in account are above line + local.account1_account_id, // locals are used to allow for global changes to multiple account assignments + # local.account2_account_id, // if hard coding the account ids, you would need to change them in every place you want to change + # local.account3_account_id, // these are defined in a locals.tf file, example is in this directory + # local.account4_account_id, + ] + }, + testuser : { + principal_name = "testuser" + principal_type = "USER" + principal_idp = "EXTERNAL" + permission_sets = ["ViewOnlyAccess"] + account_ids = [ // account(s) the user will have access to. Permissions they will have in account are above line + local.account1_account_id, // locals are used to allow for global changes to multiple account assignments + # local.account2_account_id, // if hard coding the account ids, you would need to change them in every place you want to change + # local.account3_account_id, // these are defined in a locals.tf file, example is in this directory + # local.account4_account_id, + ] + }, + } + + // Applications + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["testgroup"] + user_assignments = [] + tags = { ManagedBy = "Terraform" } + }, + SecondApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the Second Application" + name = "SecondApplication" + portal_options = { + sign_in_options = { + application_url = "http://example2.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication", "SecondApplication"] + scope = "sso:account:access" + } + ] + group_assignments = [] + user_assignments = ["testuser"] + } + } + +} diff --git a/locals.tf b/locals.tf index b041a53..551e077 100644 --- a/locals.tf +++ b/locals.tf @@ -170,3 +170,56 @@ locals { # ] } + +locals { + + # List of applications contained in this module + this_apps = [ + for app in var.sso_applications : app.name + ] + + # Creating a local variable by flattening the complex type related to Applications to extract a simple structure representing + # group-application assignments + apps_groups_assignments = flatten([ + for app in var.sso_applications : [ + for group in app.group_assignments : { + app_name = app.name + group_name = group + principal_type = "GROUP" + } + ] + ]) + + # Creating a local variable by flattening the complex type related to Applications to extract a simple structure representing + # user-application assignments + apps_users_assignments = flatten([ + for app in var.sso_applications : [ + for user in app.user_assignments : { + app_name = app.name + user_name = user + principal_type = "USER" + } + ] + ]) + + # Creating a local variable by flattening the complex type related to Applications to extract a simple structure representing + # apps assignments configurations + apps_assignments_configs = flatten([ + for app in var.sso_applications : { + app_name = app.name + assignment_required = app.assignment_required + } + ]) + + # Creating a local variable by flattening the complex type related to Applications to extract a simple structure representing + # app assignments access scopes + apps_assignments_access_scopes = flatten([ + for app in var.sso_applications : [ + for ass_acc_scope in app.assignments_access_scope : { + app_name = app.name + authorized_targets = ass_acc_scope.authorized_targets + scope = ass_acc_scope.scope + } + ] + ]) +} diff --git a/main.tf b/main.tf index f61050a..ad2641c 100644 --- a/main.tf +++ b/main.tf @@ -247,4 +247,74 @@ resource "aws_ssoadmin_account_assignment" "account_assignment" { target_type = "AWS_ACCOUNT" } +resource "aws_ssoadmin_application" "sso_apps" { + for_each = var.sso_applications == null ? {} : var.sso_applications + name = each.value.name + instance_arn = local.ssoadmin_instance_arn + application_provider_arn = each.value.application_provider_arn + client_token = each.value.client_token + description = each.value.description + + dynamic "portal_options" { + for_each = each.value.portal_options != null ? [each.value.portal_options] : [] + content { + visibility = portal_options.value.visibility + dynamic "sign_in_options" { + for_each = each.value.portal_options.sign_in_options != null ? [each.value.portal_options.sign_in_options] : [] + content { + application_url = portal_options.value.sign_in_options.application_url + origin = portal_options.value.sign_in_options.origin + } + } + } + } + tags = each.value.tags +} + +# 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 : + "${assignment_config.app_name}-assignment-config" => assignment_config + } + application_arn = aws_ssoadmin_application.sso_apps[each.value.app_name].application_arn + assignment_required = each.value.assignment_required +} + +# 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 : + "${app_access_scope.app_name}-${app_access_scope.scope}" => app_access_scope + } + 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 + ] + #authorized_targets = each.value.authorized_targets + scope = each.value.scope +} + +# 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 + 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 +} + +# Users assignments +resource "aws_ssoadmin_application_assignment" "sso_apps_users_assignments" { + for_each = { + 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 + 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 +} diff --git a/outputs.tf b/outputs.tf index 234989c..91496b1 100644 --- a/outputs.tf +++ b/outputs.tf @@ -14,3 +14,18 @@ output "sso_groups_ids" { value = { for k, v in aws_identitystore_group.sso_groups : k => v.group_id } description = "A map of SSO groups ids created by this module" } + +output "sso_applications_arns" { + value = { for k, v in aws_ssoadmin_application.sso_apps : k => v.application_arn } + description = "A map of SSO Applications ARNs created by this module" +} + +output "sso_applications_group_assignments" { + value = { for k, v in aws_ssoadmin_application_assignment.sso_apps_groups_assignments : k => v.principal_id } + description = "A map of SSO Applications assignments with groups created by this module" +} + +output "sso_applications_user_assignments" { + value = { for k, v in aws_ssoadmin_application_assignment.sso_apps_users_assignments : k => v.principal_id } + description = "A map of SSO Applications assignments with users created by this module" +} diff --git a/tests/05_create_apps_and_assignments.tftest.hcl b/tests/05_create_apps_and_assignments.tftest.hcl new file mode 100644 index 0000000..d53f66e --- /dev/null +++ b/tests/05_create_apps_and_assignments.tftest.hcl @@ -0,0 +1,13 @@ +run "unit_test" { + command = plan + module { + source = "./examples/create-apps-and-assignments" + } +} + +run "e2e_test" { + command = apply + module { + source = "./examples/create-apps-and-assignments" + } +} diff --git a/tests/06_existing_user_groups_and_apps.tftest.hcl b/tests/06_existing_user_groups_and_apps.tftest.hcl new file mode 100644 index 0000000..1c73e0e --- /dev/null +++ b/tests/06_existing_user_groups_and_apps.tftest.hcl @@ -0,0 +1,13 @@ +run "unit_test" { + command = plan + module { + source = "./examples/existing-users-groups-create-apps" + } +} + +run "e2e_test" { + command = apply + module { + source = "./examples/existing-users-groups-create-apps" + } +} diff --git a/variables.tf b/variables.tf index b06421b..395a424 100644 --- a/variables.tf +++ b/variables.tf @@ -107,3 +107,41 @@ variable "account_assignments" { default = {} } + +# Applications +variable "sso_applications" { + description = "List of applications to be created in IAM Identity Center" + type = map(object({ + name = string + application_provider_arn = string + description = optional(string) + portal_options = optional(object({ + sign_in_options = optional(object({ + application_url = optional(string) + origin = string + })) + visibility = optional(string) + })) + status = string # acceptable values are "ENABLED" or "DISABLED" + client_token = optional(string) + tags = optional(map(string)) + assignment_required = bool # Resource: aws_ssoadmin_application_assignment_configuration + assignments_access_scope = optional( + list(object({ + authorized_targets = optional(list(string)) # List of application names + scope = string + })) + ) # Resource: aws_ssoadmin_application_access_scope + group_assignments = optional(list(string)) # Resource aws_ssoadmin_application_assignment, keeping it separated for groups + user_assignments = optional(list(string)) # Resource aws_ssoadmin_application_assignment, keeping it separated for users + })) + default = {} + validation { + condition = alltrue([ + for app in values(var.sso_applications) : + app.application_provider_arn != null && + app.application_provider_arn != "" + ]) + error_message = "The application_provider_arn field is mandatory for all applications." + } +} From a097ae6c0fe30149ed923693266a118c3f17e114 Mon Sep 17 00:00:00 2001 From: reste85 Date: Mon, 25 Nov 2024 10:21:47 +0100 Subject: [PATCH 2/6] removing locals.this_apps since it is unused --- locals.tf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/locals.tf b/locals.tf index 551e077..d996c3d 100644 --- a/locals.tf +++ b/locals.tf @@ -173,11 +173,6 @@ locals { locals { - # List of applications contained in this module - this_apps = [ - for app in var.sso_applications : app.name - ] - # Creating a local variable by flattening the complex type related to Applications to extract a simple structure representing # group-application assignments apps_groups_assignments = flatten([ From 8477ea2141cd850cf677acb2efac08cb10adfb3a Mon Sep 17 00:00:00 2001 From: reste85 Date: Mon, 25 Nov 2024 15:54:27 +0100 Subject: [PATCH 3/6] Fixing header.md linting errors --- .header.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.header.md b/.header.md index 8333fc7..df32cf4 100644 --- a/.header.md +++ b/.header.md @@ -13,7 +13,6 @@ - Customer Managed Policy Support - Dynamic Application Creation (with Portal Options, Users and Groups assignments and Assignments Access Scopes configuration) - ## Important - Locals are used to allow for global changes to multiple account assignments. If hard coding the account ids for your account assignments, you would need to change them in every place you want to reference the value. To simplify this, we recommend storing your desired account ids in [local values](https://developer.hashicorp.com/terraform/language/values/locals). See the `examples` directory for more information and sample code. @@ -196,6 +195,7 @@ module "aws-iam-identity-center" { ``` ## Basic Usage - Create Applications and assign to Users and Groups + ``` // Create desired Applications in IAM Identity Center sso_applications = { @@ -223,6 +223,7 @@ module "aws-iam-identity-center" { } } ``` + ## Contributing See the `CONTRIBUTING.md` file for information on how to contribute. From 2275196ae981626c7aca9557c891281672f645cc Mon Sep 17 00:00:00 2001 From: reste85 Date: Mon, 25 Nov 2024 16:26:55 +0100 Subject: [PATCH 4/6] Fixing docs via pre-commit --- README.md | 4 +- .../create-apps-and-assignments/README.md | 98 +++++++++++++++++++ .../README.md | 98 +++++++++++++++++++ 3 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 examples/create-apps-and-assignments/README.md create mode 100644 examples/existing-users-groups-create-apps/README.md diff --git a/README.md b/README.md index 8415b5b..6217b68 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ module "aws-iam-identity-center" { ``` ## Basic Usage - Create Applications and assign to Users and Groups + ``` // Create desired Applications in IAM Identity Center sso_applications = { @@ -223,6 +224,7 @@ module "aws-iam-identity-center" { } } ``` + ## Contributing See the `CONTRIBUTING.md` file for information on how to contribute. @@ -239,7 +241,7 @@ See the `CONTRIBUTING.md` file for information on how to contribute. | Name | Version | |------|---------| -| [aws](#provider\_aws) | 5.73.0 | +| [aws](#provider\_aws) | >= 4.35.0 | ## Modules diff --git a/examples/create-apps-and-assignments/README.md b/examples/create-apps-and-assignments/README.md new file mode 100644 index 0000000..ce9d3fd --- /dev/null +++ b/examples/create-apps-and-assignments/README.md @@ -0,0 +1,98 @@ + +This directory contains examples of using the module to **create** applications, application assignments configurations, users, groups and application assignments to both users and groups + +**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" + }, + } + + // Create desired Applications in IAM Identity Center + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + } + } + +``` + +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_ssm_parameter.account1_account_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +No inputs. + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/examples/existing-users-groups-create-apps/README.md b/examples/existing-users-groups-create-apps/README.md new file mode 100644 index 0000000..5027311 --- /dev/null +++ b/examples/existing-users-groups-create-apps/README.md @@ -0,0 +1,98 @@ + +This directory contains examples of using the module to **reference existing** users and groups and assign permissions with **AWS Managed Policies** and assign SSO Applications to users and groups + +**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" + }, + } + + // Create desired Applications in IAM Identity Center + sso_applications = { + FirstApplication : { + application_provider_arn = "arn:aws:sso::aws:applicationProvider/custom" + description = "I am the First Application" + name = "FirstApplication" + portal_options = { + sign_in_options = { + application_url = "http://example.com" + origin = "APPLICATION" + } + visibility = "ENABLED" + } + status = "ENABLED" + assignment_required = true + assignments_access_scope = [ + { + authorized_targets = ["FirstApplication"] + scope = "sso:account:access" + } + ] + group_assignments = ["Dev"] + user_assignments = ["nuzumaki"] + } + } + +``` + +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_ssm_parameter.account1_account_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +No inputs. + +## Outputs + +No outputs. + \ No newline at end of file From f221c4287892dec444d30204e9082c2336f8f2f8 Mon Sep 17 00:00:00 2001 From: Kevon Mayers Date: Sun, 1 Dec 2024 19:44:26 -0500 Subject: [PATCH 5/6] bump version, pipeline e2e test passed --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0ec25f7..b18d465 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.0.0 +v1.0.1 From f758cca7ffc05dd3f914fad03c46b3882b41c837 Mon Sep 17 00:00:00 2001 From: Kevon Mayers Date: Mon, 2 Dec 2024 13:51:56 -0500 Subject: [PATCH 6/6] bump version, pipeline e2e test passed --- VERSION | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VERSION b/VERSION index b18d465..95cf410 100644 --- a/VERSION +++ b/VERSION @@ -1 +1,3 @@ v1.0.1 + +