This pattern helps you to manage AWS IAM Identity Center permissions in one place, in simple, human-readable JSON/YAML files. And it's all logically organized in files based on users/groups so you can easily see all the access a given principal has access to. There's even a script to convert your existing permission sets and assignments into this format automatically.
With this pattern, you will be able to achieve the following defined as code:
- Using human-readable JSON, create, delete and update permission sets
- Using human-readable YAML, Create, update or delete assignments from your permission set with your target (AWS accounts, AWS Organization Units, or the entire Organization) with your federated users from your AWS IAM Identity Center Identity Store (e.g. Microsoft Active Directory)
As a rule of thumb, SSO permissions have three components: a user group (who is allowed access?), an account (where are they allowed access?), and a permission set (what access is allowed?).
This repository does NOT manage SSO permissions for the MANAGEMENT account. That is the only account that this pipeline is not able to manage, due to delegated administrator requirements. The management account has a separate set of Permission Sets and Assignments that should only be used in the management account. Alternatively, you could choose to not provision any SSO roles to the management account at all: this would align with AWS's recommendation to limit use of the management account for routine tasks.
To update the permission sets and assignments, update the JSON/YAML files in terraform/source/assignments/templates and terraform/source/permission_set/templates and commit them to your repository.
Warning: Do not assign Control Tower-owned permission sets to member accounts using this solution. That breaks the principle of separate ownership between management and member accounts, as all CT permission sets are automatically deployed in the management account by Control Tower.
Documentation of their content is detailed in the terraform/source/JSON_Structure.md file.
To deploy this solution, ensure you have the following requirements:
- A multi-account environment with AWS Organizations, with unique names for each account. Accounts can be renamed using the Management account, if Trusted Access for 'AWS Account Management' is enabled.
- A CI/CD platform (eg. GitHub Actions) capable of assuming AWS roles securely, running Python3 scripts, and running Terraform version 1.5+ (to support the use of
importblocks). - An account to serve as the Identity Center delegated administrator account.
- A double-warning here: this solution is NOT designed to be run from the management account. Designate a delegated administrator: it's an AWS best practice.
- A role that can be assumed by your pipeline to make SSO changes in the delegated administrator account. This can be provisioned using Role Vending Machine or another mechanism for granting a pipeline AWS access.
- Infrastructure for Terraform state management, such as an S3 bucket to hold TF state and DynamoDB table to manage locks.
- Stage your environment
- Verify the prerequisites above.
- Create a repository in your version control system (eg. GitHub) with a copy of this repository's code.
- If you have not done so yet, determine the delegated administrator account and configure it with the following specifications below. The term
SSO Accountbelow will refer to the delegated administrator account.- To make an account into a delegated administrator, you will need to log into the management account, navigate to the Identity Center console, go to Settings, and register your selected account as a delegated administrator.
- Additionally, this solution relies on delegating certain additional read-only Organizations permissions to the IdC delegated administrator so that it can resolve OU names into child account names. The permissions required for the delegated administrator are saved in
docs/required_org_permissions.jsonand can be provided via the Organizations console in the management account. - If you have permission sets that are used by both the management account and at least one non-management account (hereafter called "shared permission sets"), you will need to split the shared permission set into 2 permission sets: one for management and one for non-management. This is a limitation of using a delegated administrator account. This change is most easily accomplished by (in the management account) copying the shared permission set and naming the copied permission set "<ORIGINAL_NAME>_MGMTACCT", then updating the management account's assignments to reference that
_MGMTACCTpermission set instead. The scripts in this repository are designed to skip permission sets containingMGMTACCT.- The script
terraform/bootstrap/migrate_assignments_to_delegated_admin_model.pycan assist with migrating permission sets to fit the delegated administrator model. However, make sure to read the documentation in this script first to understand the implications surrounding Control Tower, Service Control Policies, and IAC carefully first.
- The script
- Tailor the Terraform Infrastructure
- Update the
backend.tffile in the root with references to your Terraform state infrastructure. - Update any region specifications to the region that contains your existing SSO Identity Store. Find and replace any values labeled
YOUR_REGION_HERE. - Remove any example templates from the
terraform/source/assignments/templatesandterraform/source/permission_sets/templatesfolder. - Update the configuration in
.github/workflows/.envwith the SSO account ID and pipeline role that will be used to deploy this infrastructure. Make sure that you are using a pipeline role with appropriate permissions to create/destroy the resources.
- Update the
- Generate imports and JSON/YAML files
- These steps are intended for the delegated administrator account. You should assume local credentials that allow you to access the delegated administrator account. The scripts will not touch
MGMTACCTresources; this will only onboard non-management resources to Terraform. - From the
terraformfolder (cd terraform), runpython3 ./bootstrap/create_permission_sets_import_manifest.py --region <YOUR CONTROL TOWER HOME REGION>to help generate import files. This will read the existing permission sets in the environment and convert them into Terraform import files and JSON permission sets. It will not import Control Tower-managed permission sets (see the script for details of which it skips). If running from the management account, add the--non-delegated-admin-modeflag to ensure that management permission sets are not skipped. - From the
terraformfolder (cd terraform), runpython3 ./bootstrap/create_assignment_import_manifest.py --region <YOUR CONTROL TOWER HOME REGION>to help generate assignment files. This will read the existing permission set assignments in the environment and convert them into Terraform import files and YAML assignment files. It will not import Control Tower-managed assignments (see the script for details of which it skips). - If you specified
--non-delegated-admin-mode, review the contents of themember_importsfolder. If the contents look good, move themember_imports/import_*.tffiles to theterraformfolder and delete themember_importsfolder.- The scripts will also generate a
management_importsfolder for reference. You may review this to understand what resources are not accessible by the delegated administrator account. However, thismanagement_importsfolder is not used for this delegated administrator solution and should be deleted.
- The scripts will also generate a
- If you did not specify
--non-delegated-admin-mode, then yourimport_*.tffiles (excluding management account imports) will automatically be moved to theterraformfolder. Review them to ensure accuracy. - Review the contents of the
terraform/import_assignments.tffile. - Review the contents of the
terraform/source/assignments/templatesandterraform/source/permission_sets/templatesfolders. These should now contain automatically-generated JSON/YAML files corresponding to your existing IdC infrastructure.
- These steps are intended for the delegated administrator account. You should assume local credentials that allow you to access the delegated administrator account. The scripts will not touch
- Configure your CI/CD pipeline
- Configure your CI/CD pipeline so that the Python data resolution script
resolve_permission_sets_and_assignments.pyis run before everyterraform planoperation.- If you do not do this,
terraformwill not have resolved data to operate on. - The data resolution script also includes a call to the validation script, so you do not need to call the validation script separately.
- If you do not do this,
- For an example of how to implement this in GitHub Actions, see the
.github/workflowsfolder, specifically theterraform.yamlfile.- For an example of how to configure a pipeline role for GitHub Actions, see the role vending machine repository.
- For an example of how to implement this in CodeBuild, see the
docs/buildspec.yamlfile. - For an example of how to implement this in GitLab, see the
.gitlabfolder. - You may need to adapt these examples to meet your enterprise's CI/CD needs.
- Configure your CI/CD pipeline so that the Python data resolution script
- Validate and Optimize
- Note: If you are running
terraformlocally to test your setup, you must runresolve_permission_sets_and_assignments.pylocally before running anyterraform planoperations. Otherwise,terraformwill not have the resolved data to refer to. - Commit the files generated by the Python scripts to a feature branch of your repository. If using the GitHub workflows provided in
.github/workflows, this will trigger a new pipeline run to perform aterraform plan. - Validate your
terraform planoutput. There should ONLY be imports, and potentially changes totags_allif you keep the defaultsso_pipeline=truetag. - Merge the code to the
mainbranch if the changes look accurate. If using the GitHub workflows provided in.github/workflows, merging tomainwill trigger a new pipeline run to perform aterraform apply. - Remove the import files (
import_assignments.tf,import_inline_policies.tf, etc.) after the pipeline's first complete run. Imports can cause errors if they refer to resources that have been deleted (eg. if you import a assignment, then update the code to delete that assignment, attempting to import that assignment using a stale import block would cause an error). Make sure to commit the removal of the files. - [Optional] Consider consolidating assignments by OU. For a given user/permission set, the import script will create one assignment item per account. If you grant access to all accounts in an OU, you can instead put the OU ID (or
'ROOT'for all accounts) as the target instead of the account ID. Note that OU-based assignments will only grant access to accounts directly within the OU, not accounts in sub-OUs. Also note that'ROOT'will not create an assignment for the management account, because of the limitations of the delegated administrator account. - Verify branch protection rules. Verify that your
mainbranch has branch protection rules that, at minimum, require all changes to be done through pull requests with at least 1 approval.
- Note: If you are running
You may want to configure automation to re-run this pipeline's terraform apply action whenever there is an account created or change to the OU structure, so that the appropriate access is provisioned automatically. For an outline of how to achieve that, see .github/workflows/remote_terraform.yaml.
Note: If you don't provision any SSO roles in your management account (a best practice to not provision there!), then this section should be fairly straightforward: just don't use the string
MGMTACCTin any of your file or permission set names.
This solution splits out management of permission sets used by Management/Root account assignments from the permission sets used by non-Management/Root account assignments. Delegated administration is a security best practice recommended by the AWS Security Reference Architecture. However, the delegated administrator account is disallowed from managing the Management/Root account. Therefore, if you provision SSO roles in the management account, two pipelines and parallel sets of permission sets and assignments are required: one for delegated admin, and one for the management account. However, it is preferable to not provision SSO roles in the management account at all. Having no SSO roles in the management account reduces exposure surface and simplifies SSO management.
To reconcile where a resource belongs to, this solutions establishes a naming convention to distinguish between targets: the identifier MGMTACCT is used to indicate JSON files that are part of the management account. Files without the string MGMTACCT are assumed to be managed by the delegated administrator account. The validation stage of the pipeline will ensure that MGMTACCT permission sets are not assigned to non-management accounts, and that non-MGMTACCT permission sets are not assigned to the management account.
This is a typical Terraform pipeline, except that the main files (permission_sets_auto.tf and assignments_auto.tf) are generated dynamically from source JSON/YAML files.
This solution is abstracted so that day-to-day management only requires updating the JSON/YAML files. However, the underlying Python and Terraform actually resolves the data and applies it to the environment.
This checks every custom policy attached to permission sets and flags any overly permissive policies and common anti-patterns (eg. iam:PassRole with a wildcard Resource). These findings should be resolved prior to merging code to main. Note that this check does not flag overly permissive managed policies (eg. AdministratorAccess) is typically indicative of an over-permissioned role, but will not be flagged.
The iam_identitycenter_validation.py script is called as part of the resolve_permission_sets_and_assignments.py script. The validation script will check for any syntactical errors in the JSON/YAML files, and unsupported configurations (like two permission sets with the same name). The script will fail if there is an invalid syntax detected.
Transforming data is the primary function of the resolve_permission_sets_and_assignments.py script. Full details of the Python script can be found in the script itself. At a high level, the script will iterate through the contents of the permission_sets/templates and assignments/templates folders and resolve permission set names to permission set IDs, principal names to principal IDs, and OUs to individual account names, so that Terraform can operate on them. It will also link the assignment resources to their permission set resources in Terraform. The resulting resolved data is saved in two files: permission_sets_auto.tf and assignments_auto.tf. These Terraform files are never committed to the repository (in fact, they are explicited ignored via .gitignore). They are intermediary files that are only created during pipeline execution. If you want to see their contents, you can add a command such as cat assignments_auto.tf to the CodeBuild Action, or add configuration in the Build Project to make these Terraform files outputted as artifacts.
This solution relies on Terraform 1.5+'s import block feature. This feature is useful for migrating SSO from non-Terraform management to Terraform management, or for bringing one-off manually-created resources into Terraform's management scope. The create_*_import_manifest.py scripts in the terraform folder will generate import files split out by whether they apply to the management account or to non-management (member) accounts; this is to ensure that management resources are not managed by the delegated administrator account.
You should remove the import block files after the pipeline's first complete run. Imports can cause errors if they refer to resources that have been deleted (eg. if you import a assignment, then update the code to delete that assignment, attempting to import that assignment using a stale import block would cause an error). The opposite of an import is the terraform state rm command -- this command will remove a resource from Terraform's state so that it is no longer managed by Terraform.
Because all new accounts in a multi-account environment are moved to a specific AWS Organizational Unit, this automation will run and grant the required permission sets to the account that are specified in the assignment templates as code. Large environments might see a slow down due the amount of API request to AWS Identity Center. Throttling is being managed by Terraform (assignment stage) and boto3 SDK Config (permission set stage).
- This pipeline will manage (create, update and delete) only Permission Sets that are specified in it. Control Tower permission sets will not be modified.
- You will have multiple JSON/YAML templates in the same folder (both for permission sets and assignments). Assignments files should be split into individual files per principal (user/group) for clarity.
- When you remove a template, the pipeline will remove the assignment / permission set
- If you remove an entire assignment YAML block, the pipeline will delete the assignment from AWS IAM Identity Center
- You can't remove a permission set that is assigned to an AWS account
- You can’t manage a permission set that is associated to the Management Account (the assignment script will skip any assignments to the management account)
- You can’t manage predefined (AWS-managed) permission sets type
- You can't have multiple permission sets with the same name
- You can't have multiple assignments with the same SID
- If you change the Permission Set name, it will create a new one and delete the old one. If doing this, make sure to update any assignments that referenced the old name.
IMPORTANT: When you are using Customer Managed Policies and/or Customer Managed Permission Boundaries, you need to ensure that the policies are already created in AWS accounts you plan to deploy your permission set (this limitation does not apply for inline policies).
See CONTRIBUTING for more information.
This is adapted from https://github.com/aws-samples/aws-iam-identity-center-pipeline. It improves on that solution by moving the pipeline to a single-stage.
This artifact is published as AWS Prescriptive Guidance. You can find it with the title How to deploy IAM Identity Center (SSO) via a Single-Stage Terraform Pipeline.
- Benjamin Morris (primary developer)
- Andre Cavalcante (developer of this project's inspiration)
- Todd O'Boyle (security reviewer and unit test developer)