Skip to content

Enable multi-account deployments #82

Description

@heeki

Enable multi-account deployments

Overview

Add the ability to deploy agents across AWS accounts with Loom operating in a shared services account. Today, Loom deploys agents exclusively into the account where the backend runs — AWS_ACCOUNT_ID is a single environment variable, boto3 clients use the default credential chain, and IAM roles are created in the deployment account only. This issue introduces a deployment target model where admins register additional accounts and users select them when deploying agents.

Cross-account deployments require an STS AssumeRole flow: Loom's backend assumes a pre-provisioned role in the target account to create IAM execution roles, upload artifacts, manage secrets, and call AgentCore APIs. This issue includes the cross-account role setup to simplify enablement for new accounts.

Requirements

R1: Account Registration and Management

Admins should be able to register additional AWS accounts as deployment targets. Each account registration includes:

  • Account ID (required): The 12-digit AWS account ID.
  • Display name (required): A human-readable name (e.g., "Production - Payments").
  • Line of business (required): Organizational grouping (e.g., "Payments", "Risk", "Platform").
  • Environment (optional): Deployment tier label (e.g., "dev", "staging", "production").
  • Cross-account role ARN (required): The IAM role in the target account that Loom will assume for deployments (arn:aws:iam::{account_id}:role/loom-deployment-role).
  • External ID (required): A unique identifier for the cross-account trust relationship to prevent confused deputy attacks.
  • Supported regions (required): List of AWS regions where AgentCore deployments are allowed in this account.
  • Enabled flag: Whether this account is currently available as a deployment target.

Admins should be able to list, update, and deactivate registered accounts. Deactivating an account should not affect existing deployed agents — it only prevents new deployments to that account.

The Loom backend's own account should be automatically available as a deployment target (the "local" account) without requiring explicit registration.

Backend changes:

  • New model: backend/app/models/account.pyDeploymentAccount table with the fields above.
  • New router: backend/app/routers/accounts.py — CRUD endpoints for account management (POST /api/accounts, GET /api/accounts, PATCH /api/accounts/{id}, DELETE /api/accounts/{id}).
  • Admin-only access: Account management endpoints should be restricted to admin users.

R2: Deployment Target Selection

Users should be able to select a target account when deploying an agent (both custom agent and managed agent harness flows). The selection should support browsing by metadata — not just raw account IDs:

  • Account selector: Dropdown or searchable list showing display name, line of business, environment, and account ID.
  • Filtering: Filter accounts by line of business and environment.
  • Region selector: After selecting an account, show only the regions that account supports.
  • Default behavior: If no target account is selected, deploy to the local account (current behavior).

The selected target account ID and region should be stored on the agent record and used throughout the deployment lifecycle (role creation, artifact upload, AgentCore API calls, invocation).

Frontend changes:

  • frontend/src/components/AgentRegistrationForm.tsx — Add deployment target section above existing fields for both "Custom Agent" and "Managed Agent" forms.
  • New component for the account selector with metadata display and filtering.
  • frontend/src/api/accounts.ts — API client for account CRUD endpoints.

Backend changes:

  • backend/app/models/agent.py — Add target_account_id field (nullable; null means local account). Add cross_account_role_arn field to store the role used for this deployment.
  • backend/app/routers/agents.py — Accept target_account_id in deploy requests. Look up the DeploymentAccount record to resolve the cross-account role ARN, external ID, and validate the requested region is supported.

R3: Cross-Account Deployment Execution

When deploying to a non-local account, the backend must assume the target account's cross-account role before performing deployment operations. This affects the following operations:

  1. IAM role creation (backend/app/services/iam.py): Create the agent's execution role in the target account using assumed credentials.
  2. Artifact upload (backend/app/services/deployment.py): Upload the agent zip to an S3 bucket in the target account. The target account must have a designated artifact bucket (stored in DeploymentAccount or discoverable via convention, e.g., loom-artifacts-{account_id}).
  3. Secret management (backend/app/services/deployment.py): Store agent secrets in the target account's Secrets Manager.
  4. AgentCore API calls (backend/app/services/deployment.py, backend/app/services/agentcore.py): Call bedrock-agentcore-control and bedrock-agentcore APIs in the target account/region.
  5. Agent invocation (backend/app/routers/agents.py): When invoking a cross-account agent, assume the role to call invoke_agent_runtime or invoke_harness in the target account.

Implementation approach:

  • New utility: backend/app/services/sts.py — STS AssumeRole helper that accepts a role ARN and external ID, returns a boto3 Session with temporary credentials. Handle credential caching and expiration (STS tokens are valid for up to 1 hour).
  • Modify all boto3 client construction in deployment and invocation paths to accept an optional session parameter. When a session is provided (cross-account), use it instead of the default credential chain.
  • The cross-account role assumption should happen at the start of the deployment background task and the assumed session should be passed through to all downstream operations.

R4: Cross-Account Role Setup

Provide a CloudFormation template that target account admins can deploy to set up the required cross-account trust relationship. This template should create:

  1. Deployment role (loom-deployment-role): The role that Loom's backend assumes.
    • Trust policy: Allow sts:AssumeRole from Loom's backend account with the external ID condition.
    • Permissions: IAM role creation/management, S3 artifact bucket access, Secrets Manager access, AgentCore control plane and data plane API access, CloudWatch Logs and X-Ray for observability.
  2. Artifact S3 bucket (optional): A bucket for storing agent deployment artifacts, with a lifecycle policy to clean up old artifacts.
  3. Outputs: Export the role ARN, external ID, and bucket name for easy registration in Loom.

Files:

  • New template: iac/cross-account/template.yaml — CloudFormation template parameterized with the Loom backend account ID and external ID.
  • New makefile target: make cross-account.deploy — Deploy the cross-account setup template to a target account.
  • Documentation in the template describing the permissions granted and the setup flow.

The setup flow should be:

  1. Admin deploys the CloudFormation template in the target account, providing Loom's account ID.
  2. Template outputs the role ARN and external ID.
  3. Admin registers the account in Loom using these values.
  4. Loom validates the cross-account role assumption works before marking the account as active.

R5: Agent Lifecycle Operations Across Accounts

All existing agent lifecycle operations must work correctly for cross-account agents:

  • Refresh (POST /api/agents/{id}/refresh): Assume the target account role before calling get_agent_runtime or get_harness to fetch status.
  • Delete (DELETE /api/agents/{id}): Assume the target account role before deleting the runtime/harness, endpoint, execution role, secrets, and artifacts in the target account.
  • Update/Redeploy: If supported, assume the target account role before updating the runtime configuration.

The agent record already stores region and account_id (parsed from the ARN). For cross-account agents, look up the DeploymentAccount record by target_account_id to get the cross-account role ARN needed for these operations.

Handle the case where a DeploymentAccount record has been deactivated or deleted but agents still exist in that account — lifecycle operations should still work using the cross_account_role_arn stored on the agent record itself, not just the account registration.

Implementation Guidance

Database Schema

New table: deployment_accounts

id: UUID (primary key)
account_id: String (unique, indexed)
display_name: String
line_of_business: String (indexed)
environment: String (nullable)
cross_account_role_arn: String
external_id: String
supported_regions: JSON (list of strings)
artifact_bucket: String (nullable)
enabled: Boolean (default true)
created_at: DateTime
updated_at: DateTime

Agent table additions:

target_account_id: String (nullable, FK to deployment_accounts.account_id)
cross_account_role_arn: String (nullable — snapshot at deploy time for resilience)

STS Session Management

# backend/app/services/sts.py
def get_cross_account_session(role_arn: str, external_id: str, region: str) -> boto3.Session:
    """Assume a cross-account role and return a boto3 Session with temporary credentials."""
    sts = boto3.client("sts")
    response = sts.assume_role(
        RoleArn=role_arn,
        ExternalId=external_id,
        RoleSessionName="loom-deployment",
        DurationSeconds=3600,
    )
    credentials = response["Credentials"]
    return boto3.Session(
        aws_access_key_id=credentials["AccessKeyId"],
        aws_secret_access_key=credentials["SecretAccessKey"],
        aws_session_token=credentials["SessionToken"],
        region_name=region,
    )

IAM Permissions for Loom Backend

The Loom backend's ECS task role (backend/iac/ecs.yaml) needs sts:AssumeRole permission scoped to the cross-account deployment roles. Add a policy that allows assuming roles matching a naming convention (e.g., arn:aws:iam::*:role/loom-deployment-role) or use a resource tag condition.

Out of Scope

  • Multi-region Loom backend deployment (Loom itself runs in one account/region; it deploys agents cross-account).
  • Cross-account artifact sharing via S3 bucket policies (each target account has its own bucket).
  • Federated identity or SSO integration for account access.
  • Cost allocation or chargeback per target account.
  • Automated cross-account role rotation or credential vending.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions