Deploy IAM Roles across an AWS Organization as code
En entornos con múltiples cuentas de AWS, la gestión de roles puede ser un desafío. AWS IAM nos ofrece una solución robusta para gestionarlo en cada cuenta, pero cuando se trata de implementar roles de manera consistente en todas las cuentas de una organización, la tarea puede volverse compleja.
En este post vamos a ver cómo desplegar roles de IAM de forma automática en todas las cuentas de una organización de AWS como código, usando CloudFormation, Organizations y Terraform.
Es bastante común tener herramientas que necesiten tener unos permisos de IAM determinados en todas las cuentas donde las queramos implementar, como por ejemplo una herramienta interna que necesite acceder a todas nuestras cuentas. En el momento en el que tengamos que actualizar estos permisos, mantenerlo es todo un desafío. Así que con ayuda de AWS Organizations y AWS CloudFormation StackSets podemos solucionarlo.
Para poder explicar los pasos, vamos a utilizar un ejemplo concreto.
Nuestro objetivo es desplegar el role security-cspm
, con dos policies:
SecurityAudit
: AWS managed policy.security-cspm-sechub-import
: Customer managed policy, con los siguientes permisos:
Requisitos
Necesitaremos tener una organización de AWS creada y nuestras cuentas como miembros de esta organización.
Una vez tengamos la organización, necesitaremos habilitar la opción de Activar todas las funciones. Si gestionáis vuestra organización como código , podéis crear la organización y habilitar todas las funciones con el recurso aws_organizations_organization:
Enable Trusted Access
A continuación necesitaremos habilitar la integración de AWS CloudFormation StackSets con AWS Organizations.
Este paso, por alguna razón que desconocemos, require una acción manual . Habilitando el trusted access desde Terraform, o desde el menú de organizations, no es suficiente.
Es necesario ir a la consola de CloudFormation > StackSets y darle a Activate trusted access.
Si habéis usado Terraform para crear vuestra organización, necesitaréis añadir el siguiente service principal en el argumento aws_service_access_principals del recurso aws_organizations_organization, e.g:
resource "aws_organizations_organization" "org" {
aws_service_access_principals = [
...
"member.org.stacksets.cloudformation.amazonaws.com",
...
]
feature_set = "ALL"
}
Warning
Recordad que se debe incluir el nuevo service principal junto con los existentes. Para verificar los que ya están habilitados puedes usar el siguiente comando:
Delegar CloudFormation StackSets
Para evitar realizar los despliegues desde la cuenta management de nuestra organización, delegaremos la administración de StackSets a otra cuenta, desde la que crearemos gestionaremos los recursos de CloudFormation necesarios.
Tip
Recordad que debemos intentar reducir el uso de la cuenta de management todo lo posible. Well Architected Framework - Security Pillar SEC01-BP02
Para delegar el servicio podéis usar el siguiente recurso de Terraform:
resource "aws_organizations_delegated_administrator" "stacksets" {
account_id = var.security_account_id
service_principal = "member.org.stacksets.cloudformation.amazonaws.com"
}
O también podéis usar el siguiente comando si no gestionáis la cuenta de management as code:
aws organizations register-delegated-administrator \
--service-principal=member.org.stacksets.cloudformation.amazonaws.com \
--account-id="securityAccountId"
Esta acción solo es necesaria hacerla una vez, y desde una única región. Podéis comprobar que se ha delegado con el siguiente comando:
aws organizations list-delegated-administrators \
--service-principal=member.org.stacksets.cloudformation.amazonaws.com
Output
{
"DelegatedAdministrators": [
{
"Id": "222222222222",
"Arn": "arn:aws:organizations::111111111111:account/o-myorgid/222222222222",
"Email": "[email protected]",
"Name": "security-account",
"Status": "ACTIVE",
"JoinedMethod": "CREATED",
"JoinedTimestamp": "2024-10-11T16:30:05.938000+02:00",
"DelegationEnabledDate": "2024-10-11T16:48:07.033000+02:00"
}
]
}
Desplegar Cross-Account IAM Roles
Una vez tenemos el servicio integrado con AWS Organizations y delegado a nuestra cuenta, lo primero que tendremos que hacer será definir los recursos en un template de CloudFormation, para ello crearemos un fichero role.yaml, que después leeremos desde Terraform. El siguiente fichero declara el role y las policies de IAM que queremos:
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
RoleName:
Type: String
Description: IAM Role Name
RoleDescription:
Type: String
Description: IAM Role Description
Default: ""
PolicyName:
Type: String
Description: IAM Managed read and write policy name
PolicyDescription:
Type: String
Description: IAM Managed Policy Description
Default: ""
IAMPath:
Type: String
Description: IAM Role and Policy Path
Default: "/"
TrustedAccount:
Type: String
Description: AWS trusted account ID
TrustedRole:
Type: String
Description: Allowed IAM Role ARN
Resources:
Role:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Ref RoleName
Description: !Ref RoleDescription
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS:
!Ref TrustedAccount
Action:
- 'sts:AssumeRole'
Condition:
ArnLike:
'aws:PrincipalArn': !Ref TrustedRole
Path: !Ref IAMPath
ManagedPolicyArns:
- !Ref RolePolicy
- arn:aws:iam::aws:policy/SecurityAudit
RolePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: !Ref PolicyName
Description: !Ref PolicyDescription
Path: !Ref IAMPath
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: SecurityHubImport
Effect: Allow
Action:
- securityhub:BatchImportFindings
Resource: '*'
A continuación necesitaremos definir el StackSet de CloudFormation, usando el fichero que acabamos de crear como template:
data "aws_caller_identity" "this" {}
resource "aws_cloudformation_stack_set" "cspm_role" {
name = "security-cspm-role"
description = "Deploy Security CSPM Role across all organization accounts"
permission_model = "SERVICE_MANAGED" // (1)!
call_as = "DELEGATED_ADMIN" // (2)!
auto_deployment {
enabled = true // (3)!
}
capabilities = [
"CAPABILITY_NAMED_IAM", // (4)!
]
template_body = file("${path.root}/role.yaml") // (5)!
parameters = { // (6)!
RoleName = "security-cspm"
RoleDescription = "Audit and Import findings to SecurityHub"
PolicyName = "security-cspm-sechub-import"
PolicyDescription = "Allow import SecurityHub findings"
IAMPath = "/security/cspm/"
TrustedAccount = data.aws_caller_identity.this.id // (7)!
TrustedRole = "arn:aws:iam::${data.aws_caller_identity.this.id}:role/my-source-role"
}
}
- Permite desplegar recursos de CloudFormation en todas las cuentas con un role gestionado por AWS Organizations.
- Especifica que se actúa desde la cuenta delegada.
- Permitirá desplegar el role automáticamente en las cuentas nuevas que añadamos a la organización.
- Necesario cuando el template contiene recursos de IAM manejados por nosotros.
- El template que hemos creado anteriormente.
- Definimos los parámetros definidos en nuestro template
role.yaml
. - Usamos el data source para pasar la cuenta desde la que ejecutamos este código automáticamente.
Y por último definimos las instancias de StackSet que crearan el recurso en las cuentas:
data "aws_organizations_organization" "this" {}
resource "aws_cloudformation_stack_instances" "cspm_role" {
stack_set_name = aws_cloudformation_stack_set.cspm_role.name
call_as = "DELEGATED_ADMIN"
deployment_targets {
organizational_unit_ids = [ data.aws_organizations_organization.this.roots[0].id ] // (1)!
}
}
- Usamos el data source para pasar el organization ID automáticamente.
Tip
Es recomendable usar los argumentos operation_preferences
para mejorar la eficiencia al crear stacks en organizaciones con bastantes cuentas. Esto nos permitirá poder paralelizar cuentas. Como ejemplo:
resource "aws_cloudformation_stack_set" "cspm_role" {
...
operation_preferences {
max_concurrent_percentage = 50
failure_tolerance_percentage = 50
}
...
}
resource "aws_cloudformation_stack_instances" "cspm_role" {
...
operation_preferences {
concurrency_mode = "SOFT_FAILURE_TOLERANCE"
max_concurrent_percentage = 50
}
...
}
Una vez tenemos el código, podremos ejecutar un terraform plan
& terraform apply
y ya lo tendremos!
A modo de resumen, os dejamos el siguiente diagrama con la arquitectura que hemos creado:
Terraform Module
Si queréis simplificar todo este proceso podéis crear un módulo de Terraform como el que hemos publicado. De esta forma podréis reutilizarlo todo el código simplemente cambiando el template y los parámetros.
El módulo está disponible en el registry de Terraform, por lo que podéis usarlo desde vuestro código de la siguiente forma:
data "aws_caller_identity" "this" {}
data "aws_organizations_organization" "this" {}
module "cspm_role" {
source = "unicrons/organization-iam-role/aws"
stack_set_name = "security-cspm-role"
stack_set_description = "Deploy Security CSPM Role across all organization accounts"
template_path = "${path.root}/role.yaml"
template_parameters = {
RoleName = "security-cspm"
RoleDescription = "Audit and Import findings to SecurityHub"
PolicyName = "security-cspm-sechub-import"
PolicyDescription = "Allow import SecurityHub findings"
IAMPath = "/security/cspm/"
TrustedAccount = data.aws_caller_identity.this.id
TrustedRole = "arn:aws:iam::${data.aws_caller_identity.this.id}:role/my-source-role"
}
organizational_unit_ids = [ data.aws_organizations_organization.this.roots[0].id ]
}
Para ver todas las opciones de configuración disponibles, echad un ojo a los parámetros de entrada.
Y esto es todo amigos! Si os queda alguna duda o tenéis algún comentario no dudéis en escribirnos.
Saludos, y que la fuerza os acompañe.