> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aviatrix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AWS IAM Roles and Policies

> Aviatrix uses AWS IAM roles to give the Controller and gateways the permissions they need to build and secure your cloud network. This reference describes the aviatrix-role-ec2 and aviatrix-role-app roles and policies, how to audit and update them, and how to harden the trust relationship and scope permissions for a stronger security posture.

Aviatrix uses AWS IAM roles to give the Controller and gateways the permissions
they need to build and secure your cloud network.

For AWS access accounts, Aviatrix recommends authenticating with IAM roles
instead of static access keys. IAM roles avoid long-lived credentials and are
the default onboarding method when the Controller runs in AWS. Aviatrix also
supports access key and secret authentication, and this method is required when
the Controller runs in a different cloud than AWS (for example, Azure or GCP)
and manages an AWS account, because no AWS EC2 instance is available to attach
an IAM role to. In this case, leave the IAM roles checkbox unmarked when you add
the AWS access account, and provide an access key and secret instead.

<a href={"/docs/enterprise/" + "10.1" + "/guides/platform-administration/aviatrix-account#setting-up-additional-access-accounts-for-different-clouds"}>Setting Up Additional Access Accounts for Different Clouds</a>
shows the CoPilot and Controller steps for adding an AWS access account.

<a href={"/docs/enterprise/" + "10.1" + "/reference/general/access-key"}>
  Account with Access Key for AWS China Accounts
</a>

describes the access-key onboarding method.

<a id="iam-policies-aws" />

## Overview

<a href={"/docs/enterprise/" + "10.1" + "/guides/controlplane/getting-started-guide-aws"}>The provided CloudFormation templates</a>
create two IAM roles and their policies when you onboard an AWS account:

| Role                | Trusted By                                                                           | Purpose                                                                                                                                          |
| ------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `aviatrix-role-ec2` | the EC2 service (`ec2.amazonaws.com`)                                                | Instance role attached to the Controller and to gateways. Its job is to let the instance call `sts:AssumeRole` into the application role.        |
| `aviatrix-role-app` | an AWS principal (see [Trust Relationships (Default)](#trust-relationships-default)) | The application role. Holds the ec2, elasticloadbalancing, s3, route53, and iam permissions the Controller uses to build and operate the fabric. |

Managed policies:

* **`aviatrix-assume-role-policy`** attaches to `aviatrix-role-ec2` and grants
  `sts:AssumeRole` on `aviatrix-*`, plus marketplace metering and
  `s3:GetBucketLocation`.
* **`aviatrix-app-policy`** attaches to `aviatrix-role-app` and grants the full
  permission set described in
  [The Application Permission Policy](#the-application-permission-policy).

The Controller runs as `aviatrix-role-ec2`, assumes `aviatrix-role-app`
(cross-account for managed accounts), and then performs AWS API calls. The
Controller does the bulk of the AWS work; a gateway needs very few runtime
permissions, though not zero (see
[Gateway Logging to CloudWatch](#gateway-logging-to-cloudwatch)). Earlier
releases required broader gateway permissions, and Aviatrix has progressively
reduced that runtime dependency to improve security posture.

<Note>
  Keep the `aviatrix-app-policy` **action set** consistent across the primary
  account and every secondary account, especially if you maintain it manually
  (see [Custom and Scoped Permission
  Policies](#custom-and-scoped-permission-policies)). The roles themselves vary
  slightly by account: the trust relationship on `aviatrix-role-app` differs
  between the primary and secondary accounts by default, and differs further if
  you harden it (see [Trust Relationships
  (Default)](#trust-relationships-default) and [Hardening the Trust
  Policy](#hardening-the-trust-policy)).
</Note>

## The Two IAM Roles in Detail

### aviatrix-role-ec2 (Instance Role)

This role trusts only the EC2 service so it can act as an instance profile:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": ["ec2.amazonaws.com"] },
      "Action": ["sts:AssumeRole"]
    }
  ]
}
```

The `aviatrix-assume-role-policy` permission policy, as shipped:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["sts:AssumeRole"],
      "Resource": "arn:aws:iam::*:role/aviatrix-*"
    },
    {
      "Effect": "Allow",
      "Action": ["aws-marketplace:MeterUsage", "s3:GetBucketLocation"],
      "Resource": "*"
    }
  ]
}
```

### aviatrix-role-app (Application Role)

The trust policy on this role controls which principal may assume it. Scoping
that trust to the Controller's instance role is Aviatrix's primary hardening
recommendation (see [Hardening the Trust Policy](#hardening-the-trust-policy)).
The permission policy attached to this role is `aviatrix-app-policy`.

## Trust Relationships (Default)

<a href={"/docs/enterprise/" + "10.1" + "/guides/platform-administration/aviatrix-account#primary-access-account"}>The primary access account</a>
is the first account added to the Controller. If the Controller needs to build
connectivity in an AWS account other than its own, you create a secondary access
account, which requires IAM roles, policies, and a trust relationship back to
the primary account.

By default, the CloudFormation templates configure `aviatrix-role-app` to trust
account root (`:root`):

* **Primary (Controller) account:** trusts its own `:root`.
* **Secondary (gateway) account** (`aviatrix-secondary-account-iam.json`, with
  `OtherAccountNumber` set to the Controller account): trusts both its own
  `:root` and the Controller account's `:root`:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::<GATEWAY_ACCOUNT>:root",
          "arn:aws:iam::<CONTROLLER_ACCOUNT>:root"
        ]
      },
      "Action": ["sts:AssumeRole"]
    }
  ]
}
```

Trusting `:root` means any suitably permissioned identity in the account can
assume the application role, including the gateway instance role
(`aviatrix-role-ec2`), which is allowed to assume `aviatrix-*`. That default is
reasonable for a dedicated-account deployment. For cleaner isolation between the
Controller and its gateways, scope this trust to the Controller's role instead.
This is Aviatrix's recommended best practice (see
[Hardening the Trust Policy](#hardening-the-trust-policy)).

Inspect the current trust on any account:

```bash theme={null}
aws iam get-role --role-name aviatrix-role-app \
  --query 'Role.AssumeRolePolicyDocument.Statement[].Principal'
```

## The Application Permission Policy

The default `aviatrix-app-policy` for the latest software version is published
at
[`IAM_access_policy_for_CloudN.txt`](https://s3-us-west-2.amazonaws.com/aviatrix-download/IAM_access_policy_for_CloudN.txt).
Both the CloudFormation and Terraform onboarding paths reference this file as
the default, and it is the reference for the current permission set. It is a
generic template built for broad compatibility. Aviatrix ships it as the default
but designed it to be customized: scope and harden it (see
[Custom and Scoped Permission Policies](#custom-and-scoped-permission-policies))
to fit your environment.

## Gateway Logging to CloudWatch

When you enable CloudWatch logging, the gateway itself ships its logs, and this
does not use `aviatrix-role-app`. The gateway's CloudWatch agent assumes a
dedicated CloudWatch role (the CloudWatch Log role ARN you configure,
`cw_role_arn`) using the gateway's instance role, and the posting permissions
(`logs:CreateLogGroup`, `logs:CreateLogStream`, `logs:PutLogEvents`) live on
that role instead. Two consequences follow:

* Trust-pinning and policy-scoping of `aviatrix-role-app` (see
  [Hardening the Trust Policy](#hardening-the-trust-policy) and
  [Custom and Scoped Permission Policies](#custom-and-scoped-permission-policies))
  do not affect CloudWatch logging.
* The gateway instance role (`aviatrix-role-ec2`) must retain `sts:AssumeRole`
  to the CloudWatch role. The default `aviatrix-*` assume policy covers an
  `aviatrix-`named CloudWatch role; if you tighten the gateway role's assume
  policy below that, also allow the CloudWatch role ARN.

## Auditing and Updating Policies

Aviatrix audits each onboarded account and can update its policy from either
CoPilot or the Controller.

### Prerequisites

* An AWS access account already onboarded to the Controller.
* Admin access to CoPilot or the Aviatrix Controller.

### From CoPilot

<Steps>
  <Step title="Open the Cloud Account page">
    Go to **Cloud Resources > Cloud Account**.
  </Step>

  <Step title="Select the accounts to audit or update">
    Select the checkboxes for the AWS accounts you want to audit, or the
    accounts with IAM policies you want to update. You can select multiple
    AWS accounts.
  </Step>

  <Step title="Run the action">
    Click the **Actions** menu and select **Audit** or **Update IAM
    Policy**. You can also run an audit from the More{" "}

    <img src="https://mintcdn.com/aviatrix-14b37c43/VY6W0iTAMUQkrobS/images/security/threedot.png?fit=max&auto=format&n=VY6W0iTAMUQkrobS&q=85&s=9498dd71f99faa81563f9d3f0baf4463" alt="More menu icon" className="inline-icon" width="24" height="34" data-path="images/security/threedot.png" />

    {" "}

    menu.
  </Step>
</Steps>

<Note>
  The **Update IAM Policy** option applies only to AWS accounts; if you select
  non-AWS cloud accounts when updating policies, Aviatrix skips them during the
  update. AWS saves the current policy version and retains up to 5 latest
  non-default versions.
</Note>

### From the Aviatrix Controller

<Steps>
  <Step title="Open Access Accounts">
    Select **Accounts > Access Accounts** from the left-hand menu.
  </Step>

  <Step title="Audit the account">
    Select an AWS account and click **Audit** near the top of the page. If
    the account needs an update, the Account Audit text at the top of the
    page reads "\[Account Name] is not using the latest IAM policy."
  </Step>

  <Step title="Update the policy">
    If the account is not using the latest IAM policy, click **Update
    Policy** to update it.
  </Step>
</Steps>

The audit reports on two independent things:

* **The permission policy.** It compares the attached `aviatrix-app-policy`
  action set (not resources) against the Controller's latest. A missing action
  reports "not using the latest IAM policy — Missing policy list (…)" with
  status `Pass*`, and **Update Policy** adds the missing actions. The audit also
  requires a managed policy literally named `aviatrix-app-policy`; permissions
  attached inline or under another name report "No policy associated to
  aviatrix-role-app."
* **The trust relationship.** The audit also checks the trust, and today it
  passes only if the account's own account number appears in the trust
  principals. This has a side effect on hardening; see the callout under
  [Known Issue: Audit False Positive on Secondary Accounts](#known-issue-audit-false-positive-on-secondary-accounts).

As of version 9.0, the policy audit compares the policy's action set only, not
resource scoping. A customized policy that keeps the same actions while
narrowing resources continues to pass the action check and is recognized as
customized. When a later version introduces new required actions, the Controller
appends them to your customized policy instead of overwriting it, so your
customization persists across upgrades.

## Custom IAM Role Names

If the primary access account uses a custom EC2 IAM role name for the
Controller, every secondary IAM-based access account must use an identical EC2
IAM role name. The primary and secondary access accounts must use identical role
names when both of the following are true:

* The primary access account uses custom IAM roles.
* The secondary account does not use custom gateway IAM roles.

**Example:** The Controller uses `custom-role-app` and `custom-role-ec2` on a
secondary access account. `custom-role-ec2` also exists on the primary account,
because that is where the Controller runs. When you launch a gateway under the
secondary access account, the Controller takes the primary access account's EC2
role name (`custom-role-ec2`) and passes it to the API call that creates the
instance. That API call refers to a role on the secondary CSP account, not the
role of the primary account.

## Hardening the Trust Policy

**Recommended best practice.** Scope `aviatrix-role-app`'s trust to the
Controller's instance role instead of `:root`. This creates clean isolation
between the Controller, which manages the account, and the gateways, and is the
configuration Aviatrix recommends for production:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<CONTROLLER_ACCOUNT>:role/aviatrix-role-ec2"
      },
      "Action": ["sts:AssumeRole"]
    }
  ]
}
```

```bash theme={null}
aws iam update-assume-role-policy --role-name aviatrix-role-app \
  --policy-document file://app-trust-hardened.json
```

The Controller continues to assume the role cross-account as its own
`aviatrix-role-ec2`; only that role can. This keeps the control plane
(Controller) and data plane (gateways) cleanly separated.

Hardening behaves differently depending on your topology:

* **Gateways in a separate account (recommended):** scoping the trust gives
  clean role isolation. The Controller's role lives in a different account, and
  only it can assume the application role.
* **Gateways in the Controller account:** the gateways share the same instance
  role you are scoping to, so trust scoping alone does not separate the two.
  Running the Controller and gateways in separate accounts, the recommended
  model, gives the cleanest isolation.

With the trust scoped, the Controller continues to build and manage the account
normally, because it assumes the role as its own `aviatrix-role-ec2`. A
principal running as the gateway's own instance role is limited to what that
role allows and cannot assume `aviatrix-role-app`. ExternalId is a third-party
delegated-access control; scoping the trust to the Controller's role is the
self-managed equivalent.

### Known Issue: Audit False Positive on Secondary Accounts

<Warning>
  The account audit's trust check requires the account's own account number to
  appear in the trust. After you scope a secondary/gateway account's trust to
  the Controller role, the audit reports "does not have a trusted relationship
  with its own account" for that account.
</Warning>

**Why it is safe to ignore:** This is a cosmetic false positive. The audit only
reads the trust and never reverts it, so the finding has no functional effect.
The Controller/primary account is unaffected, because its pinned role lives in
that same account.

**Optional workaround (to show a green audit):** Add the account's own
`aviatrix-role-app` ARN to the trust alongside the Controller role:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::<CONTROLLER_ACCOUNT>:role/aviatrix-role-ec2",
          "arn:aws:iam::<THIS_ACCOUNT>:role/aviatrix-role-app"
        ]
      },
      "Action": ["sts:AssumeRole"]
    }
  ]
}
```

The self-reference to `aviatrix-role-app` is inert, because only something
already running as that role could match it. It satisfies the audit with no real
impact on your permission scope and does not grant the gateway instance role any
access; the isolation still holds.

**Status:** A future release will fix this so the Controller-role-pinned trust
passes the audit natively.

## Custom and Scoped Permission Policies

Trust scoping isolates the roles; scoping `aviatrix-app-policy` is a further,
optional layer of least privilege that narrows what the application role can do.
Aviatrix supports customer-customized policies. Scope incrementally and test
each change per feature.

### Tier 1 (Highest Impact): Scope the IAM Self-Update Permissions

**Policy self-update, scoped to its own ARN.** By default,
`iam:CreatePolicyVersion` grants access on `Resource: "*"`, which allows edits
to any managed policy in the account. Scoping it to the role's own policy is a
recommended tightening and resolves the common scanner finding (for example,
from Wiz) on this permission:

```json theme={null}
{
  "Effect": "Allow",
  "Action": ["iam:CreatePolicyVersion", "iam:DeletePolicyVersion"],
  "Resource": "arn:aws:iam::<ACCOUNT>:policy/aviatrix-app-policy"
}
```

With this scoping, the role can version only its own policy, not others in the
account.

For the tightest security posture, you can remove these two permissions entirely
instead of scoping them. Review these caveats first:

* The Controller's Audit/Update Policy function requires these permissions to
  maintain `aviatrix-app-policy`. Without them, the policy no longer updates
  automatically when a new software version or feature introduces additional
  permission requirements.
* The account audit then reports the policy as out of date. Because a failed
  audit surfaces as an account health state, external account monitoring can
  raise false-positive alerts on these accounts even though they operate
  correctly.
* You maintain the policy externally instead: incorporate any new permission
  requirements, published in
  [`IAM_access_policy_for_CloudN.txt`](https://s3-us-west-2.amazonaws.com/aviatrix-download/IAM_access_policy_for_CloudN.txt),
  into your customized policy and apply it across the primary account and all
  secondary accounts.

**PassRole and instance-profile management, scoped to `aviatrix-*`:**

```json theme={null}
{
  "Effect": "Allow",
  "Action": [
    "iam:PassRole",
    "iam:AddRoleToInstanceProfile",
    "iam:CreateInstanceProfile",
    "iam:DeleteInstanceProfile",
    "iam:RemoveRoleFromInstanceProfile",
    "iam:TagInstanceProfile"
  ],
  "Resource": [
    "arn:aws:iam::<ACCOUNT>:role/aviatrix-*",
    "arn:aws:iam::<ACCOUNT>:instance-profile/aviatrix-*"
  ]
}
```

<Note>
  Keep `iam:CreateServiceLinkedRole` on `Resource: "*"` if present. AWS controls
  service-linked-role names (for example, ELB's), so scoping this action breaks
  their creation.
</Note>

These scopings retain everything the Controller needs to launch gateways:
passing `aviatrix-role-ec2`, creating and attaching its instance profile, and
creating service-linked roles.
