Adam Divall

Walkthrough Guides and Other Useful Information on AWS

AWS Landing Zone Accelerator - Part 5: Identity & Access Management

2025-03-03 18 min read Walkthroughs Adam Divall

Welcome back to our ongoing exploration of the AWS Landing Zone Accelerator (LZA)!

We’ve made some solid headway together. As you’ll recall, in Part 1, we kicked things off by introducing the LZA and highlighting its key benefits for building a robust and well-managed AWS environment. We then rolled up our sleeves in Part 2, walking through the essential steps of setting up your AWS Organization and creating those all-important new accounts. In Part 3, we delved into the LZA’s global settings, learning how to establish standardized configurations across your entire AWS organization, ensuring consistency and streamlined management. And as we just covered in Part 4, we’ve recently taken a detour into the foundational networking capabilities.

Now, we’re shifting gears to tackle a crucial and often intricate aspect of cloud security: Identity and Access Management (IAM). In this upcoming post, we’ll dive deep into how the LZA simplifies and strengthens your IAM posture. We’ll explore how it facilitates effective user access management, enforces the principle of least privilege, and implements robust authentication mechanisms across your multi-account AWS environment. Get ready to unravel the LZA’s IAM features and discover how to build a secure and well-governed access control framework.

Centralised Single Sign-On Management with AWS IAM Identity Center

Single Sign-On (SSO) allows users to access multiple applications with a single set of credentials, eliminating the need to remember numerous passwords.

Now, why’s that important, especially when you’re dealing with multiple AWS accounts in an AWS Organisation? Well, it boils down to this:

  • Enhanced User Experience: SSO streamlines the login process, significantly improving user productivity and reducing frustration. Strengthened Security: By centralising authentication, SSO mitigates the risk of weak passwords, facilitates the implementation of multi-factor authentication, and simplifies the revocation of access for departing personnel.
  • Simplified Administration: SSO provides a centralised management platform, alleviating the complexities of managing access across numerous accounts.
  • Regulatory Compliance: SSO assists organisations in meeting compliance requirements by providing comprehensive audit trails and enabling consistent security policy enforcement.
  • Essential for AWS Organizations: SSO offers centralised access control, scalable solutions for growing environments, reduced administrative overhead, and enhanced adherence to the principle of least privilege.

Update the LZA configuration:

  • Open the iam-config.yaml file in your local copy of the aws-accelerator-config repository.
  • Add the identityCenter section and make sure it looks something like this:
identityCenter:
  name: example-org-sso
  delegatedAdminAccount: Audit
  identityCenterPermissionSets:
    - name: AdministratorAccess
        description: The Permission Set for AdministratorAccess
        policies:
        inlinePolicy: iam-policies/protect-main-branch.json
        awsManaged:
          - arn:aws:iam::aws:policy/AdministratorAccess
        sessionDuration: 60
    - name: SecurityAdmins
        description: Permission Set for Security Admins
        policies:
        inlinePolicy: iam-policies/security-admins.json
        awsManaged:
          - arn:aws:iam::aws:policy/ReadOnlyAccess
        sessionDuration: 60
    - name: DevOpsAdmins
        description: Permission Set for DevOps Admins
        policies:
        inlinePolicy: iam-policies/devops-admins.json
        awsManaged:
          - arn:aws:iam::aws:policy/PowerUserAccess
        sessionDuration: 60
    - name: BillingAdmins
        description: Permission Set for Billing Admins
        policies:
        inlinePolicy: iam-policies/billing-admins.json
        awsManaged:
          - arn:aws:iam::aws:policy/job-function/Billing
        sessionDuration: 60
    - name: ReadOnly
        description: The Permission Set for Read Only Access
        policies:
        awsManaged:
          - arn:aws:iam::aws:policy/ReadOnlyAccess
        sessionDuration: 60
  identityCenterAssignments:
    - name: AdministratorAccess-Management
      permissionSetName: AdministratorAccess
      principals:
        - type: GROUP
          name: AWS-Management-AdministratorAccess
      deploymentTargets:
        accounts:
          - Management
    - name: SecurityAdmins-Management
      permissionSetName: SecurityAdmins
      principals:
        - type: GROUP
          name: AWS-Management-SecurityAdmins
      deploymentTargets:
        accounts:
            - Management
    - name: DevOpsAdmins-Management
      permissionSetName: DevOpsAdmins
      principals:
        - type: GROUP
            name: AWS-Management-DevOpsAdmins
      deploymentTargets:
        accounts:
            - Management
    - name: BillingAdmins-Management
      permissionSetName: BillingAdmins
      principals:
        - type: GROUP
            name: AWS-Management-BillingAdmins
      deploymentTargets:
        accounts:
            - Management
    - name: ReadOnly-Management
      permissionSetName: ReadOnly
      principals:
        - type: GROUP
            name: AWS-Management-ReadOnly
      deploymentTargets:
        accounts:
            - Management

This configuration sets up Identity Center to manage user access centrally. It creates different permission sets with varying access levels and assigns them to groups in the Management account. We’ll assume you’ve already federated your Identity Center instance with Microsoft Entra ID. If not, it’s a straightforward process – you can find detailed instructions here.

Once that’s set up, Identity Center automatically syncs groups from Entra ID using SCIM. This ensures that users have the appropriate permissions based on their existing roles in your organization. This approach reinforces the principle of least privilege and provides a structured, secure way to manage access across your AWS environment.

Create the IAM Policies

  • Create a JSON file with your IAM Policies (e.g., iam-policies/protect-main-branch.json). These files will outlines the specific actions allowed or denied by your IAM policies.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ProtectMainBranch",
            "Effect": "Deny",
            "Action": [
                "codecommit:GitPush",
                "codecommit:DeleteBranch",
                "codecommit:PutFile",
                "codecommit:MergeBranchesByFastForward",
                "codecommit:MergeBranchesBySquash",
                "codecommit:MergeBranchesByThreeWay",
                "codecommit:OverridePullRequestApprovalRules"
            ],
            "Resource": "arn:aws:codecommit:eu-west-1:${MANAGEMENT_ACCOUNT_ID}:aws-accelerator-config",
            "Condition": {
                "StringEqualsIfExists": {
                    "codecommit:References": [
                        "refs/heads/main"
                    ]
                },
                "Null": {
                    "codecommit:References": "false"
                }
            }
        }
    ]
}

This policy enforces a workflow where changes to the main branch must go through pull requests and approvals, rather than being directly committed. It’s a common practice to protect the main branch from accidental or unauthorised changes, ensuring code stability and quality.

{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "codepipeline:PutApprovalResult"
        ],
        "Resource": "*"
      },
      {
        "Effect": "Allow",
        "Action": [
          "controltower:Get*",
          "controltower:List*"
        ],
        "Resource": "*"
      }
    ]
}

This policy allows a user to approve CodePipeline deployments and view information related to AWS Control Tower.

{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Deny",
          "Action": [
              "ec2:PurchaseReservedInstancesOffering",
              "savingsplans:CreateSavingsPlan"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Deny",
          "Action": [
              "codecommit:GitPush",
              "codecommit:PutFile",
              "codecommit:CreateCommit",
              "codecommit:MergeBranchesByFastForward",
              "codecommit:MergeBranchesBySquash",
              "codecommit:MergeBranchesByThreeWay",
              "codecommit:DeleteRepository"
          ],
          "Resource": "arn:aws:codecommit:eu-west-1:${MANAGEMENT_ACCOUNT_ID}:team-idc-app"
      },
      {
          "Effect": "Allow",
          "Action": [
              "codecommit:GitPush",
              "codecommit:GitPull",
              "codecommit:UploadArchive",
              "codecommit:CreateCommit",
              "codecommit:Create*",
              "codecommit:MergeBranches*",
              "codecommit:MergePull*",
              "codecommit:PostComment*",
              "codecommit:PutComment*",
              "codecommit:PutFile",
              "codecommit:PutRepositoryTriggers",
              "codecommit:UntagResource",
              "codecommit:UpdateApproval*",
              "codecommit:UpdateComment",
              "codecommit:UpdateDefaultBranch",
              "codecommit:UpdatePullRequestDescription",
              "codecommit:UpdatePullRequestTitle",
              "codecommit:UpdateRepository*"
          ],
          "Resource": "*"
      },
      {
          "Sid": "ProtectMainBranch",
          "Effect": "Deny",
          "Action": [
              "codecommit:GitPush",
              "codecommit:DeleteBranch",
              "codecommit:PutFile",
              "codecommit:MergeBranchesByFastForward",
              "codecommit:MergeBranchesBySquash",
              "codecommit:MergeBranchesByThreeWay",
              "codecommit:OverridePullRequestApprovalRules"
          ],
          "Resource": "arn:aws:codecommit:eu-west-1:${MANAGEMENT_ACCOUNT_ID}:aws-accelerator-config",
          "Condition": {
              "StringEqualsIfExists": {
                  "codecommit:References": [
                      "refs/heads/main"
                  ]
              },
              "Null": {
                  "codecommit:References": "false"
              }
          }
      },
      {
          "Effect": "Deny",
          "Action": [
              "codepipeline:PutApprovalResult"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Deny",
          "Action": [
              "ec2:CreateVpc",
              "ec2:DeleteVpc",
              "ec2:CreateSubnet",
              "ec2:DeleteSubnet",
              "ec2:ModifyVpcAttribute",
              "ec2:ModifySubnetAttribute"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "ec2:AuthorizeSecurityGroupIngress",
              "ec2:AuthorizeSecurityGroupEgress",
              "ec2:RevokeSecurityGroupIngress",
              "ec2:RevokeSecurityGroupEgress"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "support:*"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": [
            "iam:CreateOpenIDConnectProvider",
            "iam:GetOpenIDConnectProvider",
            "iam:ListOpenIDConnectProviders",
            "iam:UpdateOpenIDConnectProviderThumbprint",
            "iam:DeleteOpenIDConnectProvider",
            "iam:TagOpenIDConnectProvider",
            "iam:UntagOpenIDConnectProvider",
            "iam:CreateRole",
            "iam:GetRole",
            "iam:ListRoles",
            "iam:UpdateRole",
            "iam:DeleteRole",
            "iam:TagRole",
            "iam:UntagRole",
            "iam:GetPolicy",
            "iam:GetPolicyVersion",
            "iam:ListAttachedRolePolicies",
            "iam:ListRolePolicies",
            "iam:AttachRolePolicy",
            "iam:UpdateAssumeRolePolicy",
            "iam:PutRolePolicy",
            "iam:CreatePolicy",
            "iam:CreatePolicyVersion",
            "iam:DeletePolicy",
            "iam:DeletePolicyVersion",
            "iam:SetDefaultPolicyVersion",
            "iam:ListPolicyVersions",
            "iam:ListGroupPolicies",
            "iam:ListGroups",
            "iam:ListGroupsForUser",
            "iam:ListPolicies",
            "iam:ListEntitiesForPolicy",
            "iam:ListPoliciesGrantingServiceAccess",
            "iam:ListMFADevices",
            "iam:ListUsers",
            "iam:ListUserPolicies",
            "iam:ListVirtualMFADevices",
            "iam:TagPolicy",
            "iam:UntagPolicy"
          ],
          "Resource": "*",
          "Condition": {
              "ArnNotLike": {
                  "aws:PrincipalArn": [
                      "arn:aws:iam::*:role/AWSReservedSSO_AdministratorAccess_*",
                      "arn:aws:iam::*:role/AWSReservedSSO_AWSAdministratorAccess_*"
                  ]
              }
          }
      },
      {
          "Effect": "Deny",
          "Action": [
              "iam:CreatePolicy",
              "iam:AttachUserPolicy",
              "iam:AttachGroupPolicy",
              "iam:AttachRolePolicy",
              "iam:UpdateAssumeRolePolicy",
              "iam:PutRolePolicy"
          ],
          "Resource": "*",
          "Condition": {
              "ArnEquals": {
                  "iam:PolicyARN": [
                      "arn:aws:iam::aws:policy/AdministratorAccess",
                      "arn:aws:iam::aws:policy/AWSDirectConnectFullAccess",
                      "arn:aws:iam::aws:policy/AmazonVPCFullAccess",
                      "arn:aws:iam::aws:policy/IAMFullAccess",
                      "arn:aws:iam::aws:policy/AWSCloudTrailFullAccess",
                      "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
                  ]
              }
          }
      }
  ]
}

This IAM policy prioritises security and control by restricting high-impact actions while granting permissions for developers to work effectively.

  • Cost Control: Prevents creating or modifying resources that could incur significant costs (Reserved Instances, Savings Plans).
  • Code Protection: Safeguards specific CodeCommit repositories, especially the main branch of aws-accelerator-config by enforcing code reviews and approvals.
  • Network Stability: Limits changes to core network infrastructure (VPCs, subnets).
  • IAM Safeguards: Restricts creation and attachment of overly permissive IAM policies and prevents modification of sensitive roles.

At the same time, it enables:

  • Developer Flexibility: Allows developers to perform most CodeCommit actions, manage security groups, and interact with AWS Support.
  • Controlled IAM Management: Permits creation and management of IAM roles and OpenID Connect providers with some restrictions.

Essentially, this policy strikes a balance between security and developer agility. It prevents potentially harmful actions while allowing developers to perform their day-to-day tasks.

{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "ec2:AcceptReservedInstancesExchangeQuote",
          "ec2:CancelReservedInstancesListing",
          "ec2:CreateReservedInstancesListing",
          "ec2:DescribeHostReservations",
          "ec2:DescribeReservedInstancesOfferings",
          "ec2:DescribeHostReservationOfferings",
          "ec2:DescribeReservedInstancesModifications",
          "ec2:DescribeReservedInstances",
          "ec2:DescribeReservedInstancesListings",
          "ec2:GetHostReservationPurchasePreview",
          "ec2:GetReservedInstancesExchangeQuote",
          "ec2:ModifyReservedInstances",
          "ec2:PurchaseReservedInstancesOffering",
          "ec2:PurchaseHostReservation",
          "rds:DescribeReservedDBInstances",
          "rds:DescribeReservedDBInstancesOfferings",
          "rds:PurchaseReservedDBInstancesOffering",
          "savingsplans:CreateSavingsPlan",
          "savingsplans:DescribeSavingsPlans",
          "savingsplans:ListTagsForResource",
          "savingsplans:TagResource",
          "savingsplans:UntagResource"
        ],
        "Resource": "*"
      }
    ]
}

This policy gives users the ability to manage and utilise various cost-saving options across EC2 and RDS, enabling them to optimise spending on AWS resources.

Save your changes:

  • Use these commands to save your changes and update the LZA configuration:
git add .
git commit -m "Adding the IAM Identity Center Configuration"
git push

Breakglass Access

Break-glass access is a security practice that provides a method for authorized users to gain elevated access to systems or resources in exceptional circumstances, such as during emergencies or when normal authentication methods are unavailable. It’s like having a spare key locked in a glass box – you only break the glass (use the break-glass account) when absolutely necessary.  

The LZA (Landing Zone Accelerator) helps organizations set up a secure and well-governed multi-account AWS environment. Break-glass access is crucial in such an environment for several reasons:  

  • Emergency Access: If the primary authentication system (like IAM Identity Center) fails or becomes compromised, break-glass accounts provide a way to regain access and maintain business continuity.  
  • Incident Response: During security incidents, designated personnel may need elevated permissions to investigate and mitigate the issue. Break-glass access enables this without granting excessive privileges during normal operations.  
  • Disaster Recovery: In disaster scenarios, break-glass access can be vital for restoring critical systems and data.  
  • Auditing and Accountability: Break-glass access should be tightly controlled and monitored. The LZA can help enforce logging and auditing requirements, ensuring that any use of break-glass accounts is tracked and reviewed.  

Update the LZA configuration:

  • Open the iam-config.yaml file in your local copy of the aws-accelerator-config repository.
  • Add the userSets, groupSets, policySets and roleSets sections and make sure it looks something like this:
userSets:
  - deploymentTargets:
      accounts:
        - Management
    users:
      - username: BreakGlassAdminUser
        group: BreakGlassAdmins
groupSets:
  - deploymentTargets:
      accounts:
        - Management
    groups:
      - name: BreakGlassAdmins
        policies:
          customerManaged:
            - BreakGlassAdminsPolicy
            - RequireMFAPolicy
policySets:
  - deploymentTargets:
      accounts:
        - Management
    policies:
      - name: BreakGlassAdminsPolicy
        policy: iam-policies/breakglass-role-policy.json
      - name: RequireMFAPolicy
        policy: iam-policies/require-mfa-policy.json
roleSets:
  - deploymentTargets:
      organizationalUnits:
        - Root
      excludedAccounts:
        - Management
    roles:
      - name: BreakGlassAdminRole
        assumedBy:
          - type: principalArn
            principal: "arn:aws:iam::{{account Management}}:user/BreakGlassAdminUser"
        policies:
          awsManaged:
            - AdministratorAccess

This LZA configuration creates a special “break-glass” user in your Management AWS account. This user has:

  • Strong Security: They must use multi-factor authentication (MFA) to log in.
  • Specific Permissions: They have a custom policy that grants only the necessary permissions for emergencies.
  • Elevated Access: They can assume an administrator role in every other account within your organization. This is achieved by deploying a corresponding “break-glass” role to each account in your AWS Organization.

This setup allows for secure and controlled access in emergencies, following best practices like MFA and least privilege. It’s like having a secure backup key for your AWS environment, only to be used when absolutely necessary.

Ensure that you establish robust processes for granting access to this privileged user, alongside the technical implementation.

Create the IAM Policies

  • Create a JSON file with your IAM Policies (e.g., iam-policies/protect-main-branch.json). These files will outlines the specific actions allowed or denied by your IAM policies.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowBreakGlassAssumeRoleAccess",
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": "arn:aws:iam::*:role/BreakGlassAdminRole",
            "Condition": {
                "StringEquals": {
                    "aws:PrincipalOrgID": "${ORG_ID}"
                }
            }
        }
    ]
}

This policy enables users within your organisation to assume a specific break-glass role in any account within your organisation. This is useful for emergency access or incident response scenarios, where authorized personnel might need to assume elevated privileges to troubleshoot or resolve issues.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DenyAllExceptMFAManagement",
            "Effect": "Deny",
            "NotAction": [
                "iam:ChangePassword",
                "iam:CreateVirtualMFADevice",
                "iam:EnableMFADevice",
                "iam:GetUser",
                "iam:ListMFADevices",
                "iam:ListUsers",
                "iam:ListVirtualMFADevices",
                "iam:ResyncMFADevice",
                "iam:DeactivateMFADevice",
                "sts:GetSessionToken"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        }
    ]
}

This policy essentially says: “You can only manage your MFA settings if you haven’t set up MFA yet. Once MFA is enabled, you must use it to do anything else.” This forces users to enable and use MFA for all actions except those directly related to setting up MFA in the first place.

Save your changes:

  • Use these commands to save your changes and update the LZA configuration:
git add .
git commit -m "Adding the BreakGlass User Configuration"
git push

Key IAM Roles for Your AWS Organization

Break-glass access isn’t the only reason you might want IAM roles in every account. Think of it like this: sometimes you need to give special permissions to tools or services, not just people.

For example, you might use a security tool like Prowler to check for vulnerabilities. This tool needs permission to access different parts of your AWS accounts to do its job. Similarly, if you’re using AWS Systems Manager to manage your EC2 instances, you’ll need specific IAM roles for that too.

We’ll dive deeper into Prowler and security best practices later in this blog series, but for now, just remember that IAM roles are versatile tools. They can be used for various purposes beyond just human access, especially when it comes to automating tasks and improving security across your AWS Organization.

For the purposes of adding the roles required for Prowler and Systems Manager Access, i’ll provide examples below.

Update the LZA configuration:

  • Open the iam-config.yaml file in your local copy of the aws-accelerator-config repository.
  • Update the policySets and roleSets sections and make sure it looks something like this:
policySets:
  - deploymentTargets:
      accounts:
        - Audit
    policies:
      - name: Prowler-Task-Role-Policy
        policy: iam-policies/prowler-task-role-policy.json
  - deploymentTargets:
      organizationalUnits:
        - Root
    policies:
      - name: Prowler-Org-Role-Policy
        policy: iam-policies/prowler-org-role-policy.json
roleSets:
  - deploymentTargets:
      organizationalUnits:
      - Root
    roles:
      - name: EC2-Default-SSM-Role
        instanceProfile: true
        assumedBy:
          - type: service
            principal: ec2.amazonaws.com
        policies:
          awsManaged:
            - AmazonSSMManagedInstanceCore
            - CloudWatchAgentServerPolicy
  - deploymentTargets:
      accounts:
      - Audit
    roles:
      - name: Prowler-Task-Role
        instanceProfile: false
        assumedBy:
          - type: service
            principal: ecs-tasks.amazonaws.com
        policies:
          customerManaged:
            - Prowler-Task-Role-Policy
  - deploymentTargets:
      organizationalUnits:
        - Root
    roles:
      - name: Prowler-Org-Role
        instanceProfile: false
        assumedBy:
          - type: principalArn
            principal: "arn:aws:iam::{{account Audit}}:role/Prowler-Task-Role"
        policies:
          awsManaged:
            - "SecurityAudit"
            - "job-function/ViewOnlyAccess"
          customerManaged:
            - Prowler-Org-Role-Policy

This configuration enables the use of Prowler for security assessments across your organisation by setting up appropriate roles and policies. It also streamlines the management of EC2 instances by deploying a default SSM role. This combination enhances both security posture and operational efficiency within your AWS environment.

Save your changes:

  • Use these commands to save your changes and update the LZA configuration:
git add .
git commit -m "Adding the Prowler & SSM Instance Roles Configuration"
git push

Centralised Root Access Management

AWS Organizations offers Centralised Root Access Management, a secure and efficient way to manage root user access across your AWS accounts. This feature allows you to centrally manage the root user access for all your member accounts. This means controlling the most powerful user in each AWS account, who has full privileges within an AWS account, from a central location. To enhance security and simplify administration, this feature allows you to manage the root user, who has unrestricted access within an AWS account, from a central location in your organisation.

The key benefits of using Centralised Root Access Management include:

  • Reduced Security Risks: Centralised Root Access Management mitigates the risks associated with individual root user credentials by allowing you to delete those credentials and prevent direct access.
  • Centralised Control: Manage root user access for all your accounts from a single location, simplifying administration and improving consistency.
  • Secure by Default: New accounts are provisioned without root user credentials, enhancing security from the outset.
  • Enhanced Compliance: Centralised auditing and policy enforcement help you meet compliance requirements more effectively.

Update the LZA configuration:

  • Open the customizations-config.yaml file in your local copy of the aws-accelerator-config repository.
  • Update the customizations section and make sure it looks something like this:
customizations:
  cloudFormationStacks:
    - name: EnableCentralRootAccessManagement
      description: Enable Central Root Access Management
      deploymentTargets:
        accounts:
            - Management
      regions:
        - eu-west-1
      template: custom-config-templates/central-root-access-management.yaml
      runOrder: 3
      terminationProtection: true

Create the CloudFormation Template

  • Create a YAML file with your CloudFormation Code to define the Infrastructure that is required to deploy the Centralised Root Access Management (e.g., custom-config-templates/central-root-access-management.yaml).
AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda function to remove root user credentials from member accounts
Parameters:
  pS3BucketName:
    Type: String
    Description: Name of the S3 bucket containing the Lambda deployment package
    Default: Lambda-Artifacts
  pS3KeyName:
    Type: String
    Description: S3 key (path) to the Lambda deployment package
    Default: CentralisedRootAccessManagement.zip
  pOrganizationAccessRole:
    Type: String
    Default: AWSControlTowerExecution
    Description: Cross Account Role
  pVersion:
    Type: String
    Default: 1.0.0
    Description: For Lambda updates
Resources:
  rCentralizedRootAccess:
    Type: Custom::CentralizedRootAccess
    Properties:
      ServiceToken: !GetAtt rCentralizedRootAccessFunction.Arn
    DependsOn: rLambdaExecutionRole
  rCentralizedRootAccessFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: lambda_function.lambda_handler
      Role: !GetAtt rLambdaExecutionRole.Arn
      Code:
        S3Bucket: !Sub pS3BucketName-${AWS::AccountId}-${AWS::Region}
        S3Key: !Ref pS3KeyName
      Runtime: python3.12
      Timeout: 300
      MemorySize: 128
      Environment:
        Variables:
          LOG_LEVEL: INFO
          ORGANIZATION_ACCESS_ROLE: !Ref pOrganizationAccessRole
          VERSION: !Ref pVersion
  rLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: CentralizedRootAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - organizations:EnableAWSServiceAccess
                  - organizations:DisableAWSServiceAccess
                  - iam:EnableOrganizationsRootCredentialsManagement
                  - iam:DisableOrganizationsRootCredentialsManagement
                  - iam:EnableOrganizationsRootSessions
                  - iam:DisableOrganizationsRootSessions
                Resource: '*'
        - PolicyName: S3AccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource: !Sub 'arn:aws:s3:::${pS3BucketName}/${pS3KeyName}'
Outputs:
  oLambdaFunctionArn:
    Description: ARN of the Lambda function
    Value: !GetAtt rCentralizedRootAccessFunction.Arn
    Export:
      Name: !Sub '${AWS::StackName}-${rLambdaFunctionArn}'
  oLambdaRoleArn:
    Description: ARN of the Lambda execution role
    Value: !GetAtt rLambdaExecutionRole.Arn
    Export:
      Name: !Sub '${AWS::StackName}-${rLambdaRoleArn}'

Preparing and Deploying the Custom CloudFormation Solution

  • Create an S3 Bucket in the Management Account named Lambda-Artififacts-AWSAccountID-AWSRegion.
  • Create a python file with the code to enable the Centralised Root Access Management functionality using the Boto3 APIs.
  • Create a zip file named CentralisedRootAccessManagement.zip that contains the python files and is packaged with the latest version of Boto3 since currently the version running in Lambda within AWS isn’t at the right version.
  • Upload the Zip File to the S3 Bucket that was previously created.
import boto3
import json
import logging
import urllib3
from botocore.exceptions import ClientError

# Configure logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Initialize clients
organizations_client = boto3.client('organizations')
iam_client = boto3.client('iam')
http = urllib3.PoolManager()

def send_cfn_response(event, context, response_status, response_data, physical_resource_id=None):
    """
    Send response to CloudFormation
    """
    response_body = {
        'Status': response_status,
        'Reason': f'See CloudWatch Log Stream: {context.log_stream_name}',
        'PhysicalResourceId': physical_resource_id or context.log_stream_name,
        'StackId': event['StackId'],
        'RequestId': event['RequestId'],
        'LogicalResourceId': event['LogicalResourceId'],
        'NoEcho': False,
        'Data': response_data
    }

    try:
        response = http.request(
            'PUT',
            event['ResponseURL'],
            body=json.dumps(response_body).encode('utf-8'),
            headers={'Content-Type': 'application/json'},
            retries=urllib3.Retry(3)
        )
        logger.info(f"CloudFormation response status code: {response.status}")
    except Exception as e:
        logger.error(f"Error sending response to CloudFormation: {str(e)}")
        raise

def handle_aws_operation(operation, error_message):
    """
    Generic handler for AWS operations with error handling
    """
    try:
        return operation()
    except ClientError as e:
        error_code = e.response['Error']['Code']
        error_message = f"{error_message}: {error_code} - {e.response['Error']['Message']}"
        logger.error(error_message)
        raise
    except Exception as e:
        logger.error(f"{error_message}: {str(e)}")
        raise

def enable_centralized_root_access():
    """
    Enable centralized root access management
    """
    # Step 1: Enable trusted access for IAM in Organizations
    handle_aws_operation(
        lambda: organizations_client.enable_aws_service_access(
            ServicePrincipal='iam.amazonaws.com'
        ),
        "Error enabling IAM service access in Organizations"
    )
    logger.info("Successfully enabled trusted access for IAM in Organizations")

    # Step 2: Enable root credentials management
    handle_aws_operation(
        lambda: iam_client.enable_organizations_root_credentials_management(),
        "Error enabling root credentials management"
    )
    logger.info("Successfully enabled root credentials management")

    # Step 3: Enable root sessions
    handle_aws_operation(
        lambda: iam_client.enable_organizations_root_sessions(),
        "Error enabling root sessions"
    )
    logger.info("Successfully enabled root sessions")

def disable_centralized_root_access():
    """
    Disable centralized root access management
    """
    # Step 1: Disable root sessions
    handle_aws_operation(
        lambda: iam_client.disable_organizations_root_sessions(),
        "Error disabling root sessions"
    )
    logger.info("Successfully disabled root sessions")

    # Step 2: Disable root credentials management
    handle_aws_operation(
        lambda: iam_client.disable_organizations_root_credentials_management(),
        "Error disabling root credentials management"
    )
    logger.info("Successfully disabled root credentials management")

    # Step 3: Disable trusted access for IAM in Organizations
    handle_aws_operation(
        lambda: organizations_client.disable_aws_service_access(
            ServicePrincipal='iam.amazonaws.com'
        ),
        "Error disabling IAM service access in Organizations"
    )
    logger.info("Successfully disabled trusted access for IAM in Organizations")

def lambda_handler(event, context):
    """
    Main Lambda handler for CloudFormation Custom Resource
    """
    try:
        logger.info(f"Received event: {json.dumps(event)}")

        # Validate required CloudFormation parameters
        if not all(key in event for key in ['RequestType', 'StackId', 'RequestId', 'LogicalResourceId', 'ResponseURL']):
            raise ValueError("Missing required CloudFormation parameters")

        request_type = event['RequestType']
        physical_resource_id = event.get('PhysicalResourceId', context.log_stream_name)

        if request_type in ['Create', 'Update']:
            enable_centralized_root_access()
            response_data = {
                'Message': 'Successfully enabled centralized root access management',
                'Details': {
                    'TrustedAccess': 'Enabled for iam.amazonaws.com',
                    'RootCredentialsManagement': 'Enabled',
                    'RootSessions': 'Enabled'
                }
            }
            send_cfn_response(event, context, 'SUCCESS', response_data, physical_resource_id)

        elif request_type == 'Delete':
            try:
                disable_centralized_root_access()
                response_data = {
                    'Message': 'Successfully disabled centralized root access management'
                }
                send_cfn_response(event, context, 'SUCCESS', response_data, physical_resource_id)
            except Exception as e:
                logger.warning(f"Error during cleanup: {str(e)}")
                # Still return success to allow stack deletion to proceed
                send_cfn_response(event, context, 'SUCCESS', {
                    'Message': 'Resource cleanup attempted'
                }, physical_resource_id)

        else:
            raise ValueError(f"Invalid request type: {request_type}")

    except Exception as e:
        logger.error(f"Error in lambda_handler: {str(e)}")
        send_cfn_response(event, context, 'FAILED', {
            'Error': str(e)
        }, context.log_stream_name)
        raise

    return {
        'statusCode': 200,
        'body': json.dumps({'message': 'Operation completed successfully'})
    }

This script automates the process of enabling or disabling Centralised Root Access Management in AWS Organizations. It can be used as a CloudFormation custom resource, allowing you to manage this feature as part of your infrastructure-as-code deployments. This helps ensure that root user access is managed consistently and securely across your AWS accounts.

Save your changes:

  • Use these commands to save your changes and update the LZA configuration:
git add .
git commit -m "Adding Centralised Root Access Management"
git push

Temporary Elevated Access Management

Temporary Elevated Access Management (TEAM) is a way to give users stronger permissions for a limited time, like a temporary key to a restricted area. This is important because it:

  • Increases Security: Reduces the risk of someone misusing powerful permissions.
  • Enforces Least Privilege: Users only get the access they need, when they need it.
  • Improves Tracking: Keeps a record of who requested access, who approved it, and what they did.
  • Helps with Tasks: Allows users to do things that need special permissions without always having them.

AWS offers an open-source solution for temporary elevated access management that integrates seamlessly with IAM Identity Center. You can find it on GitHub. I’ve used it myself and found it to be very effective.

This solution allows users to request elevated access on demand, but with important safeguards:

  • Existing Access Required: Users can only request elevated access for accounts where they already have some level of permission through group membership.
  • Limited Role Access: Elevated access is restricted to specific roles defined in eligibility policies, ensuring users only gain the necessary privileges.
  • Full Auditability: The solution provides detailed logs of all actions taken by users while they have elevated access, ensuring accountability and enabling thorough security reviews.

This combination of self-service, controlled elevation, and comprehensive auditing provides a flexible yet secure approach to temporary access management.

Deployment

  • To get TEAM up and running, start by checking you’ve got everything in place as described in the prerequisites section on GitHub. Then, head over to the deployment guide on GitHub and follow the instructions to install TEAM.

Right now, they use some shell scripts to get it installed. But, if you’re keen to have more control, you can actually dig into those scripts and pull out the CloudFormation templates and parameters they use. Then, you can pop those into your customizations-config.yaml file and let the LZA handle the TEAM deployment directly.

Wrapping Up

That wraps up our deep dive into Identity and Access Management within the AWS Landing Zone Accelerator!

We’ve covered a wide range of topics in this post, from centralizing user access with IAM Identity Center to implementing break-glass accounts and setting up temporary elevated access management. We’ve also explored how the LZA helps you enforce the principle of least privilege and maintain a robust security posture across your AWS organization.

By implementing these IAM best practices, you can ensure that your AWS environment is secure, well-governed, and compliant with regulatory requirements.

Stay tuned for the next installment in this series, where we’ll delve into another critical aspect of the LZA: Security. We’ll explore how the LZA helps you gain comprehensive visibility into your AWS environment and effectively detect and respond to potential security threats.

In the meantime, feel free to reach out if you have any questions or comments about IAM or the LZA.