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.
-
Access to the AWS management account. Ensure you have access to your AWS management account.
-
AWS IAM sign-in permissions.
-
Terraform Installed.
-
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:
- Create and manage AWS Dynamic Secrets
- Rotate existing IAM credentials using AWS Rotated Secret
- Operate the AWS Universal Secrets Connector
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 Organizations → Organize 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.
- 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.
- Run Terraform in each account, supplying:
trusted_principal
The IAM role ARN the Gateway runs under (in the management account).external_id
The string you copied from the Akeyless AWS Target.
- After
terraform apply
, copy the role’s ARN back into the same AWS Target and save. - 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
- Run
terraform init
andterraform apply
. - Take the Role ARN and set it in the AWS target that was created earlier under Role ARN field.
- Use that target whenever you create a Dynamic Secret, Rotated Secret, or USC.
Updated about 2 hours ago