AWS Cloud Operations & Migrations Blog

Manage your AWS multi-account environment with Account Factory for Terraform (AFT)

Independent software vendors (ISVs) are AWS Partners who build products or services using AWS. Their workloads are typically diverse and require a flexible and customizable multi-account setup. Following are some examples:

  • Backoffice workloads, which tend be deployed once and are then regularly updated, typically relying on commercial off-the-shelf software.
  • Presales workloads, which are short lived and tailored to a particular opportunity.
  • Research and Development workloads, which have specific technical requirements like testing compatibility of products with a particular feature of a cloud native services.

This blog post outlines how AWS ISV Partner Software AG approached this challenge, using AWS Control Tower Account Factory for Terraform (AFT) as the preferred tool to manage AWS accounts, AWS cloud operations services, and the platform-like shared services in its corporate multi-account AWS environment.

This blog post focusses on the solution that we built together with Software AG (SAG). With this solution, SAG was able to achieve the following:

  • The time an internal customer has to wait for a new AWS account has been reduced from 1-2 days to 25 minutes.
  • By writing a wrapper, no additional complexity for updating our AWS multi-account environment to new versions of AFT and Control Tower was introduced since we did not touch the internals of AFT.

Your needs, objectives and circumstances may be different and may need to be addressed differently. Please reach out to AWS Sales, AWS Professional Services, or to the AWS Partner Network to discuss your optimal solution.

Overview

Software AG simplifies the connected world. Founded in 1969, it helps deliver the experiences that employees, partners and customers now expect. Its technology creates the digital backbone that integrates applications, devices, data, and clouds; empowers streamlined processes; and connects “things” like sensors, devices and machines. It helps 10,000+ organizations to become a truly connected enterprise and make smarter decisions, faster. The company has over 5,000 employees across more than 70 countries and annual group revenue of over €950 million ($1,014 million).

At Software AG the Corporate Cloud team is responsible for managing and governing the AWS multi-account environment. The Corporate Cloud team had a set of key requirements they wanted to achieve:

Manage AWS accounts at scale, while having the ability to apply account-specific customizations.

  • Efficiently manage the AWS multi-account environment following a GitOps approach, driving changes to the environment from a single place.

In collaboration with AWS Professional Services, the Corporate Cloud team built a multi-account environment on AWS using AWS Control Tower Account Factory for Terraform (AFT).

In this blog post we will focus on three key topics:

  1. Account vending process with AFT Account Request Wrapper
  2. CICD for baselining all the AWS accounts enabling cross-account changes
  3. AWS IAM Identity Center with Microsoft Entra ID management

Prerequisites

In order to effectively engage with the material presented in this post, the following prior knowledge and experience is recommended:

1. AFT Account Request Wrapper


The default approach to provision a new account with AFT is to create a Terraform file, which calls the aft-account-request module passing required information. After commit and push to the account requests git repository the whole account provisioning and customization machinery is kicked off. You can view an example account request Terraform file on GitHub in human friendly HashiCorp Configuration Language (HCL).

In a larger organization such as Software AG, it is common that IT services are provided by centralized teams through ITSM Service Desk. This simplifies the IT service request, standardizes the service fulfilment and most importantly improves the end user experience SAG approached this challenge by enriching the AFT account vending mechanism, by building an integration into SAG’s ITSM solution. The integration consists of the following stages:

Implementing a new Terraform module softwareag-aws-account-request as wrapper for the native aft-account-request module. This wrapper allows inputs provided in the high-level ITSM service request form can be directly passed as inputs to the softwareag-aws-account-request module, which will validate inputs and transform them to technical inputs compatible to aft-account-request module.

  1. Using one dedicated file per account request utilizing the Terraform JSON Configuration Syntax to declare module calls of softwareag-aws-account-request. The integration with AFT-external automation frameworks reduces complexity, as every account request can be mapped to one dedicated file in the Git repository. In addition, with JSON information between systems are exchangeable.
  2. Implementing a standalone Integration API which is triggered when the customer submits an ITMS service request, extracts the information from the service request, performs validations and eventually creates a commit in aft-account-request Git repository. Manual conversion of the ITSM service request into a Terraform JSON Configuration Syntax can be automated without any human interaction.

Figure 1 illustrates the relations of the described enhancements and how they integrate to vanilla AFT implementation.

Figure 1: IT service desk invokes the Integration API, which creates a new JSON file input for “softwareag-aws-account-request”, which calls “aft-account-request” module. CodePipeline is triggered, when pushed on main branch.

Figure 1: IT service desk invokes the Integration API, which creates a new JSON file input for “softwareag-aws-account-request”, which calls “aft-account-request” module. CodePipeline is triggered, when pushed on main branch.

Figure 2 shows the difference between a module call of softwareag-aws-account-request in JSON notation matching closely to the ITSM service request form and the module call of aft-account-request in HCL with default technical inputs.

{
  "module": [
    {
      "acc-project-neptun": [
        {
          "source": "./modules/softwareag-aws-account-request",
          "short_name": "project-neptun",
          "business_criticality": "high",
          "business_unit": "RnD",
          "data_classification": "secret",
          "environment_type": "dev",
          "budget_bucket": "RnD_Project_Neptun",
          "budget_limit_amount_usd": 1000,
          "contact_information": {
            "business_contact_name": "John Doe",
            "business_contact_phone": "+1-1234-56-789",
            "business_contact_title": "Account Owner",
            "business_contact_upn": "john.doe@amazon.com",
            "technical_contact_upn": "john.doe@amazon.com"
          },
          "permission_model": [
            "AdministratorAccess"
          ],
          "service_request_id": "ITSM-SR-123",
          "workload_description": "Lorem ipsum dolor sit amet ...",
          "network": {
            "eu-central-1": {
              "connected": true,
              "create_private_dns_zone": true,
              "availability_zone_numbers": 1,
              "vpc_workloads_netmask_sizes": 26
            },
            "us-east-1": {
              "connected": true,
              "create_private_dns_zone": false,
              "availability_zone_numbers": 2,
              "vpc_workloads_netmask_sizes": 24
            }
          }
        }
      ]
    }
  ]
}
module "acc-project-neptun" {
  source = "./modules/aft-account-request"

  control_tower_parameters = {
    AccountEmail = "xxxxxxxxxx@softwareag.com"
    AccountName  = "acc-project-neptun"
    ManagedOrganizationalUnit = "Workloads"
    SSOUserEmail     = "xxxxxxxxxx@softwareag.com"
    SSOUserFirstName = "John"
    SSOUserLastName  = "Doe"
  }

  account_tags = {
    billing-budget-bucket = "RnD_Project_Neptun"
    business-contact      = "john.doe"
    business-criticality  = "high"
    business-unit         = "RnD"
    connectivity-type     = "custom"
    data-classification   = "secret"
    environment-type      = "dev"
    service-request       = "ITSM-SR-123"
    technical-contact     = "john.doe"
    workload-description  = "Lorem ipsum dolor sit amet ..."
  }

  change_management_parameters = {
    change_requested_by = "john.doe"
    change_reason       = "ITSM-SR-123"
  }

  custom_fields = {
    alternate_contact = {
      billing    = {
        email-address = "aws-billing@softwareag.com"
        name          = "AWS Billing"
        phone-number  = "+12-3456-78-9"
        title         = "AWS Billing Manager"
      }
      operations = {
        email-address = "paulw@softwareag.com"
        name          = "Paul Wannowius"
        phone-number  = "+12-3456-78-9"
        title         = "Account Owner"
      }
      security   = {
        email-address = "aws-security@softwareag.com"
        name          = "AWS Security"
        phone-number  = "+12-3456-78-9"
        title         = "AWS Security Manager"
      }
    }
    permission_model = [
      "AdministratorAccess"
    ]
    budget_limit_amount_usd = "1000"
    network = {
      "eu-central-1": {
        "connected": true,
        "create_private_dns_zone": true,
        "availability_zone_numbers": 1,
        "vpc_workloads_netmask_sizes": 26
      },
      "us-east-1": {
        "connected": true,
        "create_private_dns_zone": false,
        "availability_zone_numbers": 2,
        "vpc_workloads_netmask_sizes": 24
      }
    }
  }
  
  account_customizations_name = "WORKLOADS"
}

Building on top of that, we also want to import AWS accounts that have been created before the implementation of AWS Control Tower and AFT. The information required to import an existing account is slightly different compared to create new account, especially if the goal is also to import central platform accounts to AFT management (such as Log Archive, Audit and Organization Management). Therefore, we created another adapter module softwareag-aws-account-import that works similarly but with different inputs required, for example account level tags.

2. Cross-account Customizations


AFT has out-of-the box support for Global and Account customization to create and manage the baseline AWS resources for the account created or imported in AFT. With the help of a Jinja template file terraform providers for the same account are created at the time of pipeline execution and both customizations are applied. This dynamic provider approach is useful, unless you have dependencies between resources which exist in different accounts (especially platform accounts).

For example, Software AG faced the following scenarios considering the platform (hub) and workload (spoke) accounts:

  • Vending a new workload account and then enabling a group of users to access it via AWS identity center requires changes in the organization management account, or delegated Identity account (Identity Store Groups and Permission Set Assignments have to be created). [ This is also explained in detail in point 3. ]
  • Enabling hybrid connectivity for a new workload account requires changes in the centralized networking account (Transit Gateway VPC Attachments to be created).

Doing this with vanilla AFT means to:

  • Change the code of account-specific customizations for all three involved accounts, causing duplicate code.
  • Manually resolve dependencies and provide input parameters for resources that depend on others (e.g. Transit Gateway Id and VPC Id to create an attachment between them).
  • Manually or with re-invoke customizations execute three customization pipelines in correct order.

Deprovisioning a workload account, consequently means to revert all changes and execute multiple customization pipelines in correct order again, which may be error-prone to manage resources across accounts in AFT.

To handle this, SAG enriched AFT to support cross-account customizations by managing dependent resources in different accounts from one customization pipeline only (usually the pipeline of the for workload account). However, this only applies for a subset of resources required per workload account (e.g. Permission Set Assignments, Identity Store Groups, or Transit Gateway VPC Attachment). Static resources that are only required once are still managed by the customization pipeline of the respective platform account (e.g. IAM Identity Center, Permission Sets, or Transit Gateways).

Figure 2: Account customization pipeline renders Jinja files to create terraform providers through which the workload customization pipeline can deploy AWS resources in network or management account.

Figure 2: Account customization pipeline renders Jinja files to create terraform providers through which the workload customization pipeline can deploy AWS resources in network or management account.

Cross-account customization requires multiple declarations of the AWS Terraform Provider, as each represents credentials for one account at a time (in one dedicated AWS region). By default, each customization pipeline only comes with one default provider configuration for the Control Tower home region which gets generated dynamically on every pipeline execution by rendering a Jinja2 template (For example see the aft-providers.jinja template file for account-specific customizations). To add additional providers, you can either extend the existing Jinja2 template, or create a separate template file as required. The customization pipeline will render it automatically and replace well-known variables like for the default template.

In example, the following Jinja2 template can be used to dynamically generate providers for multiple accounts in multiple regions. The rendered result is shown below. Note that every provider has a unique alias that can be referenced in the Terraform code (see Multiple Provider Configurations documentation for more details).

{%-
  set accounts = {
    'workload' : {
       'regions' : ['eu-central-1','us-east-1']
    },
    'management' : {
       'id' : '111111111111',
       'role' : 'AWSAFTExecution',
       'regions' : ['eu-central-1']
    },
    'network' : {
       'id' : '222222222222',
       'role' : 'AWSAFTExecution',
       'regions' : ['eu-central-1','us-east-1']
    }
  }
-%}

{%- for accountkey, accountvalue in accounts.items() -%}
##########################################################################################################
# Account: {{accountkey}}{% if accountvalue.id %} ({{accountvalue.id}}){% endif %}
##########################################################################################################
{% for regionvalue in accountvalue.regions %}
provider "aws" {
  alias  = "{{accountkey}}-{{regionvalue}}"
  region = "{{regionvalue}}"
  assume_role {
    {%- if accountvalue.id is defined and accountvalue.role is defined %}
    role_arn = "arn:aws:iam::{{accountvalue.id}}:role/{{accountvalue.role}}"
    {%- else %}
    role_arn = "{{target_admin_role_arn}}"
    {%- endif %}
  }
}
{% endfor %}
{% endfor %}

This is the rendered output of the Jinja file execution, which generates the providers dynamically in the AFT pipeline:

##########################################################################################################
# Account: workload ##########################################################################################################
provider "aws" {
  alias  = "workload-eu-central-1"
  region = "eu-central-1"
  assume_role {
    role_arn = "arn:aws:iam::333333333333:role/AWSAFTExecution"
  }
}

provider "aws" {
  alias  = "workload-us-east-1"
  region = "us-east-1"
  assume_role {
    role_arn = "arn:aws:iam::333333333333:role/AWSAFTExecution"
  }
}

##########################################################################################################
# Account: management (111111111111)
##########################################################################################################

provider "aws" {
  alias  = "management-eu-central-1" 
  region = "eu-central-1"
  assume_role {
    role_arn = "arn:aws:iam::111111111111:role/AWSAFTExecution"
  }
}

##########################################################################################################
# Account: network (222222222222)
##########################################################################################################

provider "aws" {
  alias  = "network-eu-central-1"
  region = "eu-central-1"
  assume_role {
    role_arn = "arn:aws:iam::222222222222:role/AWSAFTExecution"
  }
}

provider "aws" {
  alias  = "network-us-east-1"
  region = "us-east-1"
  assume_role {
    role_arn = "arn:aws:iam::222222222222:role/AWSAFTExecution"
  }
}

3. Management of AWS IAM Identity Center integrated with Microsoft Entra ID

AWS offers the service AWS IAM Identity Center to centrally manage employee access to all our AWS accounts. One core feature, called multi-account permissions, allows the Corporate Cloud team to define permission sets, which are collections of IAM policies.  Permissions sets can be assigned to users and groups and provisioned into one or more target accounts. Behind the scenes, IAM Identity Center service will create managed IAM roles inside these accounts that will grant respective permission to access the accounts through a single sign-on portal.

Software AG use Microsoft Entra ID for central identity management. AWS IAM Identity Center allows to configure Microsoft Entra ID as external identity provider (IdP) and can automatically provision users and groups to AWS identity store, using Cross-domain Identity Management (SCIM) protocol. This outsources both user authentication and authorization to Microsoft Entra ID.

For SAG’s implementation we have following technical and non-technical requirements:

  • Provide a basic authorization model with predefined permission sets (e.g. ReadOnlyAccess, PowerUserAccess (developer), AdministratorAccess) to standardize permissions and  simplify the choice of the correct access permissions for less experienced AWS users.
  • Only configure the requires roles as requested in the IT service of a new AWS account.
  • Enable AWS account owners to manage access to accounts in a self-service manner via controlling group memberships.
  • Loose coupling between the AFT account vending process and the Microsoft Entra ID group creation process to avoid dependencies that could lead to failure of the account customization process in case group creation process faulty or temporarily not possible.

The developed solution to these requirements relies on the already presented AFT Account Request Wrapper and our approach for Cross-account Customizations.

  1. In the ITSM Service Desk users can select one or multiple roles which fits the anticipated use of the account. For every selected role a corresponding Identity Store Group will be created in AWS IAM Identity Center. The group names are derived deterministically from the service request input, for example the concatenation of a common prefix like acc followed by the account and role name (e.g. for roles reader and admin and the account name foo the groups will be named acc-foo-reader and acc-foo-admin). The actual creation of the groups is handled by the AFT account customization pipeline by calling custom Terraform module (1).
  2. After Identity Store Groups are created, AFT continues to create Permission Set Assignments to configure authorization to the newly vended account (2). This happens during the same account customization pipeline run, again by calling a custom Terraform module.
  3. Next, after the account has been successfully vended by AFT, corresponding groups have to be created in Microsoft Entra ID. In our case, an outside automation (Integration API) takes care of the Microsoft Entra ID Groups creation, and optionally if requested, already adds members to the groups (3). Important to notice that Microsoft Entra ID Groups must have the exact same names as the previously created Identity Store Groups as this will be important for the next step. (Reference for creating Microsoft Entra ID groups or Terraform resource)
  4. Finally, to enable access, the Identity Store and Microsoft Entra ID groups, which are still independent of each other, must be connected. This happens by an initial SCIM Provisioning process initiated by Microsoft Entra ID (4). In this process, the Identity Store Groups will be matched by name and linked to the corresponding Microsoft Entra ID Group (by setting the ExternalID of the Group in Identity Store to the Object ID of the group from Microsoft Entra ID). Additionally, if present, all users that are members of the Microsoft Entra ID groups get synced to Identity Store. Once this has happened access is initially granted so that people can login to the newly vended account through AWS access portal. Future changes to Microsoft Entra ID Group memberships will be automatically synced to AWS.
Figure 3: IT service desk invokes the Integration API, which also trigger the Azure group creation process along with creating a group and permission set assignment within the AWS workload account, once Microsoft Entra ID sync groups gets linked.

Figure 3: IT service desk invokes the Integration API, which also trigger the Azure group creation process along with creating a group and permission set assignment within the AWS workload account, once Microsoft Entra ID sync groups gets linked.

Conclusion

In this post, we provided insights how the Corporate Cloud team at Software AG is managing their AWS multi-account environment. The solution enables the involved parties to use already known channels and tools, i.e. internal customers request new AWS accounts like other IT services through the ITSM service desk and the Corporate Cloud Team uses pull request to apply changes to the environment.

By implementing the account provisioning end to end without adding additional AWS infrastructure, but solely relying on AFT, we have achieved these benefits:

  • The time an internal customer has to wait for a new AWS account has been reduced from 1-2 days to 25 minutes.
  • By writing a wrapper no additional complexity for updating our AWS multi-account environment to new versions of AFT and Control Tower was introduced since we did not touch the internals of AFT.

Overall the adoption of AWS Control Tower and AFT enabled the Corporate Cloud team to improve the user experience and reduced operational efforts for managing their AWS multi-account environment.

About the authors:

Sven Mentl is responsible for the Corporate Cloud team and the Identity team at Software AG. Prior to Software AG he worked as Engineering Manager at MAN Truck & Bus on customer-facing digital services for electrical busses and before that he held multiple roles at Accenture, either in the Custom Software Development unit or the Emerging Technologies unit.

Paul Wannowius is a Cloud Architect at Software AG with 14 year of experience in IT and Cloud. Before his current role, he has worked as lead IT architect and engineer of Software AG’s internal self-service portal to deploy on-premise infrastructures using various technologies (such as VMware, Kubernetes, Puppet, Terraform, etc.). Paul has a Master of Science degree in information technology issued by Darmstadt University of Applied Sciences

Deepak Vishwakarma is a Cloud Architect at AWS in the Professional Services team, focused on Cloud Infrastructure, DevOps, Microservices, and Solution architecture. With 9+ years of industry experience, Deepak is deeply committed to building technologies that empower organizations to easily integrate cloud services and drive progress. He enjoys overcoming customers’ challenges related to operations and security by getting hands-on, offering technical guidance, best practices and leadership on projects designed to ensure customer’s achieve their goals.

Lucie Wagner is an Engagement Manager at AWS Professional Services. In her role, she assists customers in the implementation of AWS cloud projects, which span from application migrations over the development of innovative machine learning models and to the establishment of Cloud Centers of Excellence. Her focus is on Independent Software Vendor, Retail, and Life Science industries

Ahmet Altunel is a Senior Cloud Architect at AWS with 14 years of experience in building cloud foundations and tackling large-scale migrations for enterprise customers. He is passionate about engineering platforms that effectively scale the cloud adoption and drive innovation for businesses as well as thriving on solving customers’ operational and security challenges, providing technical guidance, and leading design and implementation projects to ensure his customers’ success on AWS.