AWS Roles for Centralized Gateway Using Terraform

Overview

This article outlines the steps to integrate your AWS account with Akeyless Gateway, along with instructions for AWS Organization integration with relevant member accounts. This guide provides a Terraform script to create the required AWS IAM roles that grant the Gateway IAM permissions in your AWS environment when operating in a centralized deployment, where a single Gateway manages multiple AWS accounts by securely assuming cross-account roles using an External ID.

Prerequisites

To successfully integrate your AWS accounts with Akelyess, ensure the following prerequisites are met:

For Organization integration, ensure you have an existing AWS environment with AWS Organizations enabled. If you need to create one, refer to the AWS Organizations user guide.

  1. Access to the AWS management account. Ensure you have access to your AWS management account.

  2. AWS IAM sign-in permissions.

  3. Terraform Installed.

  4. An AWS IAM authentication method & Access Role on your Akeyless account, this Authentication Method can support many AWS accounts. Alternatively, you can create a dedicated auth method per account, ending with a unique access ID per account. In both cases, the RBAC in Akeyless can isolate the access permissions inside Akeyless per account.

Create the required IAM roles

In this step, you will connect to your AWS management account to apply using a Terraform template that will create the IAM role that enables the Akeyless Gateway to:

You provide one Role ARN to the Gateway, and a single AWS Target handles everything, whether all resources are in one AWS account or spread across multiple accounts with an optional external ID

Provide the parameters to identify your AWS Organization

Sign in to the AWS Management Console of your management account. Navigate to OrganizationsOrganize accounts.

Copy your AWS Management Account ID and paste it into the field.management_account

Configuration

Provision one IAM role per AWS account , each tied to a unique External ID generated by Akeyless. The Gateway uses an AWS Target that contains that account’s Role ARN and its matching External ID.

👍

TIP

To create the IAM role in the target account, make sure you have an AWS CLI profile configured for that account on the machine running Terraform.

  1. Create an AWS Target in Akeyless  for each account using the Gateway Cloud ID option. Leave Role ARN blank for now or with the default ARN. Check the External ID option and copy the random value.
  2. Run Terraform in each account, supplying:
    • trusted_principalThe IAM role ARN the Gateway runs under (in the management account).
    • external_idThe string you copied from the Akeyless AWS Target.
  3. After terraform apply, copy the role’s ARN back into the same AWS Target and save.
  4. Repeat for every AWS account you want to manage.

Create the Role in the Management Account

First, we will create the IAM-Role in the resource account using terraform:

terraform {
  required_version = ">= 1.3.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0"
    }
  }
}

provider "aws" {
  region  = var.region
}

#############################
# TRUST POLICY 
#############################
data "aws_iam_policy_document" "trust" {
  statement {
    sid     = "AllowAssumeRole"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::${var.management_account}:root"]
    }

    condition {
      test     = "ArnEquals"
      variable = "aws:PrincipalArn"
      values   = ["arn:aws:iam::${var.management_account}:role/${var.role_name}"]
    }
  }

  statement {
    sid     = "AllowDestinationToAssume"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::${var.destination_account_id}:root"]
    }
  }

  # Allow EC2 to assume
  statement {
    sid     = "AllowEC2Assume"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

#################
# CREATE THE ROLE
#################
resource "aws_iam_role" "this" {
  name               = var.role_name
  assume_role_policy = data.aws_iam_policy_document.trust.json
}

##############################################
# PERMISSIONS POLICY
##############################################
data "aws_iam_policy_document" "permissions" {
  statement {
    sid     = "AssumeSelf"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    resources = [
      "arn:aws:iam::${var.management_account}:role/${var.role_name}"
    ]
  }

  statement {
    sid     = "AssumeDestinationRole"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    resources = [
      "arn:aws:iam::${var.destination_account_id}:role/${var.destination_role_name}"
    ]
  }

  statement {
    sid     = "ManageTmpUsersInAccountA"
    effect  = "Allow"
    actions = [
      "iam:DeleteAccessKey",
      "iam:AttachUserPolicy",
      "iam:DeleteUser",
      "iam:ListUserPolicies",
      "iam:CreateUser",
      "iam:TagUser",
      "iam:CreateAccessKey",
      "iam:CreateLoginProfile",
      "iam:RemoveUserFromGroup",
      "iam:AddUserToGroup",
      "iam:ListGroupsForUser",
      "iam:ListAttachedUserPolicies",
      "iam:DetachUserPolicy",
      "iam:GetLoginProfile",
      "iam:DeleteLoginProfile",
      "iam:ListUserTags",
      "iam:ListAccessKeys"
    ]
    resources = [
      "arn:aws:iam::${var.management_account}:user/tmp.*"
    ]
  }

  statement {
    sid     = "AkeylessUSC"
    effect  = "Allow"
    actions = [
      "secretsmanager:ListSecrets",
      "secretsmanager:GetSecretValue",
      "secretsmanager:DescribeSecret",
      "secretsmanager:CreateSecret",
      "secretsmanager:UpdateSecret",
      "secretsmanager:DeleteSecret",
      "secretsmanager:PutSecretValue",
      "secretsmanager:UntagResource",
      "secretsmanager:TagResource"
    ]
    resources = ["*"]
  }
}

#############################
# CREATE & ATTACH THE POLICY
#############################
resource "aws_iam_policy" "this" {
  name   = "${var.role_name}-policy"
  policy = data.aws_iam_policy_document.permissions.json
}

resource "aws_iam_role_policy_attachment" "attach" {
  role       = aws_iam_role.this.name
  policy_arn = aws_iam_policy.this.arn
}

######################
# Output: ROLE ARN
######################
output "role_arn" {
  value       = aws_iam_role.this.arn
  description = "Created IAM Role ARN"
}
variable "management_account" {
  description = "AWS Management Account ID"
  type        = string
  default     = "123456789012"
}

variable "region" {
  description = "AWS region"
  type        = string
  default     = "<aws-region>"
 }

variable "role_name" {
  description = "IAM role name that will be created in the Management Account"
  type        = string
  default     = ""
}

variable "destination_account_id" {
  description = "Destination Account ID"
  type        = string
  default     = ""
}

variable "destination_role_name" {
  description = "Role name in the destination Account"
  type        = string
  default     = ""
}

Run terraform init and terraform apply.

Once finish, you will have a new role in the management account that trusts itself and the role that will be created in the destination account.

Create the Role in the Destination Account

Now, that we have a role in the management account, a role needs to be created in each AWS Account we want to manage from the Akeyless Gateway.

Run the following in order to create a role in the destination account:

terraform {
  required_version = ">= 1.3.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0"
    }
  }
}

# Point this provider at **Destination Account**
provider "aws" {
  region  = var.region
  profile = var.profile
}

########################################
# TRUST POLICY (Destination Account, creation-safe)
########################################
data "aws_iam_policy_document" "trust" {
  statement {
    sid     = "AllowRoleBAssumeWithExternalId"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::${var.destination_account_id}:root"] # Destination Account root
    }

    condition {
      test     = "ArnEquals"
      variable = "aws:PrincipalArn"
      values   = ["arn:aws:iam::${var.destination_account_id}:role/${var.role_name}"]
    }

    condition {
      test     = "StringEquals"
      variable = "sts:ExternalId"
      values   = [var.external_id]
    }
  }

  statement {
    sid     = "AllowRoleAAssume"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::${var.management_account_id}:role/${var.management_role_name}"]
    }
  }

  statement {
    sid     = "AllowSelfAssume"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::${var.destination_account_id}:root"] # Destination Account root
    }

    condition {
      test     = "ArnEquals"
      variable = "aws:PrincipalArn"
      values   = ["arn:aws:iam::${var.destination_account_id}:role/${var.role_name}"]
    }
  }
}

#################
# CREATE THE ROLE
#################
resource "aws_iam_role" "this" {
  name               = var.role_name
  assume_role_policy = data.aws_iam_policy_document.trust.json
}

##############################################
# PERMISSIONS POLICY (mirrors your permissions)
##############################################
data "aws_iam_policy_document" "permissions" {
  statement {
    sid     = "AssumeSelf"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    resources = [
      "arn:aws:iam::${var.destination_account_id}:role/${var.role_name}"
    ]
  }

  statement {
    sid     = "AssumeSourceRole"
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    resources = [
      "arn:aws:iam::${var.management_account_id}:role/${var.management_role_name}"
    ]
  }

  statement {
    sid     = "ManageTmpUsersInDestinationAccount"
    effect  = "Allow"
    actions = [
      "iam:DeleteAccessKey",
      "iam:AttachUserPolicy",
      "iam:DeleteUser",
      "iam:ListUserPolicies",
      "iam:CreateUser",
      "iam:TagUser",
      "iam:CreateAccessKey",
      "iam:CreateLoginProfile",
      "iam:RemoveUserFromGroup",
      "iam:AddUserToGroup",
      "iam:ListGroupsForUser",
      "iam:ListAttachedUserPolicies",
      "iam:DetachUserPolicy",
      "iam:GetLoginProfile",
      "iam:DeleteLoginProfile",
      "iam:ListUserTags",
      "iam:ListAccessKeys"
    ]
    resources = [
      "arn:aws:iam::${var.destination_account_id}:user/tmp.*"
    ]
  }

  statement {
    sid     = "AkeylessUSC"
    effect  = "Allow"
    actions = [
      "secretsmanager:ListSecrets",
      "secretsmanager:GetSecretValue",
      "secretsmanager:DescribeSecret",
      "secretsmanager:CreateSecret",
      "secretsmanager:UpdateSecret",
      "secretsmanager:DeleteSecret",
      "secretsmanager:PutSecretValue",
      "secretsmanager:UntagResource",
      "secretsmanager:TagResource"
    ]
    resources = ["*"]
  }
}

#############################
# CREATE & ATTACH THE POLICY
#############################
resource "aws_iam_policy" "this" {
  name   = "${var.role_name}-policy"
  policy = data.aws_iam_policy_document.permissions.json
}

resource "aws_iam_role_policy_attachment" "attach" {
  role       = aws_iam_role.this.name
  policy_arn = aws_iam_policy.this.arn
}

######################
# OPTIONAL: ROLE ARN
######################
output "role_arn" {
  value       = aws_iam_role.this.arn
  description = "Created IAM Role ARN (Destination Account)"
}

variable "role_name" {
  description = "IAM role name for Destination Account (the one being created)"
  type        = string
  default     = ""
}

variable "management_role_name" {
  description = "Name of the IAM role in Management Account that this role should trust"
  type        = string
  default     = ""
}

variable "destination_account_id" {
  description = "AWS Account ID where this role is created (Destination Account)"
  type        = string
  default     = ""
}

variable "management_account_id" {
  description = "AWS Account ID for Management Account (trusted account)"
  type        = string
  default     = ""
}

variable "external_id" {
  description = "ExternalId required for Destination Role assumption"
  type        = string
  default     = ""
}

variable "region" {
  description = "AWS region"
  type        = string
  default     = "<AWS-Region"
}

variable "profile" {
  description = "AWS CLI profile for Destination Account (optional if using env creds)"
  type        = string
  default     = ""
}

Run the Deployment

  1. Run terraform init and terraform apply.
  2. Take the Role ARN and set it in the AWS target that was created earlier under Role ARN field.
  3. Use that target whenever you create a Dynamic Secret, Rotated Secret, or USC.

Footer Section