Skip to content

iam architecture

Aleksej Komnenovic edited this page Jun 30, 2026 · 3 revisions

IAM Architecture

Separate roles per service, least-privilege. Defined in iam.tf within the ECS Module.

Role Architecture

flowchart LR
    ExecRole[ECS Task Execution Role\nshared] --> ManagedPolicy[AmazonECSTaskExecutionRolePolicy\nAWS managed]
    ExecRole --> SecretsRead[secrets_read policy\ndynamic]

    BRMSRole[BRMS Task Role] --> S3RW[s3_access\nread-write]
    BRMSRole --> RDS[rds_iam_connect\ndb auth=iam]
    BRMSRole --> KMS[brms_kms_access\nsecrets_provider=aws-kms]
    BRMSRole --> ExtBuckets[brms_external_buckets\nexternal_buckets]
    BRMSRole --> Bedrock[brms_bedrock_access\nAI provider=amazon-bedrock]
    BRMSRole --> BRMSExec[ecs_exec\nenable_execute_command]

    AgentRole[Agent Task Role] --> S3RO[s3_read_only\nread-only]
    AgentRole --> AgentExec[ecs_exec\nenable_execute_command]

    style RDS stroke-dasharray: 5 5
    style KMS stroke-dasharray: 5 5
    style ExtBuckets stroke-dasharray: 5 5
    style Bedrock stroke-dasharray: 5 5
    style BRMSExec stroke-dasharray: 5 5
    style AgentExec stroke-dasharray: 5 5
Loading

Task Execution Role

Shared by both BRMS and Agent. Handles image pulling and secret retrieval.

AWS Managed Policy

AmazonECSTaskExecutionRolePolicy allows:

  • ECR image pull
  • CloudWatch log creation

Secrets Read Policy (Dynamic)

Dynamically constructed based on which components are enabled:

Secret Service Condition
License key BRMS Always (BRMS enabled)
Cookie secret BRMS Always (BRMS enabled)
Secrets master key BRMS secrets_provider = "env"
AI API key BRMS AI enabled + API key provided
Custom secrets (brms.secrets) BRMS User-provided secrets
Custom secrets (agent.secrets) Agent User-provided secrets
Database credentials BRMS db auth = "secrets"

BRMS Task Role

S3 Read-Write Access

From Storage Module's s3_access policy:

  • s3:ListBucket, s3:GetBucketLocation
  • s3:GetObject, s3:PutObject, s3:DeleteObject + versions

RDS IAM Connect (conditional)

Created by the Database Module when database.auth = "iam". Allows BRMS to authenticate to Aurora using temporary IAM credentials instead of a password:

data "aws_iam_policy_document" "rds_iam_connect" {
  count = var.auth == "iam" ? 1 : 0

  statement {
    sid       = "AllowRDSIAMConnect"
    effect    = "Allow"
    actions   = ["rds-db:connect"]
    resources = [
      "arn:aws:rds-db:${data.aws_region.current[0].region}:${data.aws_caller_identity.current[0].account_id}:dbuser:${aws_rds_cluster.this.cluster_resource_id}/${var.iam_username}"
    ]
  }
}

KMS Access (conditional)

When brms.secrets_provider.type = "aws-kms", grants the BRMS task role permission to use the customer-managed KMS key for encrypting and decrypting internal secrets:

data "aws_iam_policy_document" "brms_kms_access" {
  statement {
    effect = "Allow"
    actions = [
      "kms:Encrypt",
      "kms:Decrypt",
      "kms:GenerateDataKey",
      "kms:GenerateDataKeyWithoutPlaintext",
      "kms:DescribeKey",
    ]
    resources = [local.brms_kms_key_arn]
  }
}

See Secrets Management for the KMS setup.

External Buckets (conditional)

When brms.external_buckets is provided (for multi-account deployments), grants BRMS access to S3 buckets in other AWS accounts. A single list statement and a single object-access statement cover all configured buckets via for-expressions over the bucket ARNs:

data "aws_iam_policy_document" "brms_external_buckets" {
  statement {
    sid    = "ListExternalBuckets"
    effect = "Allow"
    actions = [
      "s3:ListBucket",
      "s3:GetBucketLocation"
    ]
    resources = [for b in var.brms_external_buckets : b.arn]
  }

  statement {
    sid    = "ExternalBucketObjectAccess"
    effect = "Allow"
    actions = [
      "s3:GetObject",
      "s3:GetObjectVersion",
      "s3:PutObject",
      "s3:DeleteObject",
      "s3:DeleteObjectVersion"
    ]
    resources = [for b in var.brms_external_buckets : "${b.arn}/*"]
  }
}

Bedrock AI Access (conditional)

When BRMS AI provider = amazon-bedrock, grants access to invoke the configured foundation model. No API key needed, IAM handles authentication. The policy is scoped to the specific model ID. When a cross-region inference profile is used (model ID with a geographic prefix such as us., eu., or apac.), a second statement grants access to that profile:

data "aws_iam_policy_document" "brms_bedrock_access" {
  statement {
    sid    = "AllowBedrockFoundationModel"
    effect = "Allow"
    actions = [
      "bedrock:InvokeModel",
      "bedrock:InvokeModelWithResponseStream"
    ]
    resources = [
      "arn:aws:bedrock:*::foundation-model/${local.bedrock_base_model_id}"
    ]
  }

  dynamic "statement" {
    for_each = local.bedrock_is_inference_profile ? [1] : []
    content {
      sid    = "AllowBedrockInferenceProfile"
      effect = "Allow"
      actions = [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream"
      ]
      resources = [
        "arn:aws:bedrock:*:${local.account_id}:inference-profile/${local.bedrock_model_id}"
      ]
    }
  }
}

The region is wildcarded because cross-region inference profiles can route to any region within the geography. The model resource is scoped to the single configured model ID, not all foundation models.

See AI LLM Configuration for AI setup.

ECS Exec (conditional)

The ECS module supports ECS Exec via enable_execute_command (default false), which grants SSM permissions for interactive container shell access:

data "aws_iam_policy_document" "ecs_exec" {
  statement {
    effect = "Allow"
    actions = [
      "ssmmessages:CreateControlChannel",
      "ssmmessages:CreateDataChannel",
      "ssmmessages:OpenControlChannel",
      "ssmmessages:OpenDataChannel",
    ]
    resources = ["*"]
  }
}

Note: ECS Exec is not currently exposed as a root-module variable. The root brms/agent objects have no enable_execute_command field (see Variable System), so it stays at its false default through the standard configuration and this policy is not created. The capability exists in the ECS submodule for direct or future use.

Agent Task Role

Much simpler than BRMS:

S3 Read-Only Access

From Storage Module's s3_read_only policy:

  • s3:ListBucket, s3:GetBucketLocation
  • s3:GetObject, s3:GetObjectVersion

No write access, Agent only reads rules, never modifies them.

Lambda Execution Role

For the Database Module's IAM user setup Lambda:

  • AWSLambdaVPCAccessExecutionRole (AWS managed)
  • secretsmanager:GetSecretValue on the database credentials secret

The Lambda connects to Aurora as the master user using the username and password from Secrets Manager (via the psycopg layer), then runs CREATE USER, GRANT rds_iam, and ALTER DATABASE to provision the IAM-auth database user. It does not use IAM database authentication itself, so it is not granted rds-db:connect. That permission belongs only to the BRMS Task Role (see RDS IAM Connect above).

Storage IAM User (conditional)

When storage.auth = "secrets", an IAM user is created:

  • The bucket-scoped s3_access read-write policy attached (ListBucket and GetBucketLocation on the bucket, Get/Put/Delete object actions on its objects, see Storage Module). This is full read-write on the GoRules bucket only, not account-wide S3 access.
  • Access keys stored in Secrets Manager
  • See Storage Module for details

Policy Attachment Flow

flowchart LR
    StorageMod[Storage Module] -->|creates policies\noutputs ARNs| Root1[Root Module]
    Root1 -->|passes ARNs| ECS1[ECS Module]
    ECS1 -->|attaches to| TaskRoles[Task Roles]

    DBMod[Database Module] -->|creates policies\noutputs ARNs| Root2[Root Module]
    Root2 -->|passes ARNs| ECS2[ECS Module]
    ECS2 -->|attaches to| BRMSRole[BRMS Task Role]
Loading

Clone this wiki locally