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

# Networking permissions

> Every IAM action the network service checks, which resource each one is checked against, and the three places a policy is wider or narrower than it looks.

Networking endpoints require the IAM actions below. Some reads check both
the requested resource and its related resources.

<Info>
  The API reference shows the action on each endpoint's own page, so you do not
  have to come back here to look one up. Both come from the same place: the
  authorization call in the service, read at build time.
</Info>

## Resource shapes

Ten resource types, and the difference between them decides what a policy can
say:

| CRN                                                                         | Keyed by |
| --------------------------------------------------------------------------- | -------- |
| `crn:network:<region>:<account>:vpc/<name>`                                 | name     |
| `crn:network:<region>:<account>:vpc/<vpc>/subnet/<name>`                    | name     |
| `crn:network:<region>:<account>:vpc/<vpc>/subnet/<subnet>/interface/<name>` | name     |
| `crn:network:<region>:<account>:vpc/<vpc>/route-table/<name>`               | name     |
| `crn:network:<region>:<account>:security-group/<name>`                      | name     |
| `crn:network:<region>:<account>:internet-gateway/<name>`                    | name     |
| `crn:network:<region>:<account>:nat-gateway/<name>`                         | name     |
| `crn:network:<region>:<account>:egress-only-gateway/<name>`                 | name     |
| `crn:network:<region>:<account>:route/<id>`                                 | id       |
| `crn:network:<region>:<account>:floating-ip/<id>`                           | id       |

Routes and floating IPs have no name of their own, so they are the two that
cannot be named readably in a policy. Condition on tags for those.

The region slot is **populated**. A VPC exists in one region, so a policy
written for one region does not reach another — and `crn:network:*:…` is how
you write one that spans them deliberately.

## The actions

| Action                                                  | Call                                                                                                          | Checked against                     |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `network:ListVPCs`                                      | `GET /v1/vpcs`                                                                                                | `vpc/*`                             |
| `network:CreateVPC`                                     | `POST /v1/vpcs`                                                                                               | the new VPC's CRN                   |
| `network:GetVPC`                                        | `GET /v1/vpcs/{vpc_id}`                                                                                       | the VPC                             |
| `network:GetVPC`                                        | `GET /v1/vpcs/{vpc_id}/prefix-pools`                                                                          | the VPC                             |
| `network:UpdateVPC`                                     | `POST /v1/vpcs/{vpc_id}/prefix-pools`, `DELETE /v1/vpcs/{vpc_id}/prefix-pools/{pool_id}`                      | the VPC                             |
| `network:UpdateVPC`                                     | `PATCH /v1/vpcs/{vpc_id}`                                                                                     | the VPC                             |
| `network:DeleteVPC`                                     | `DELETE /v1/vpcs/{vpc_id}`                                                                                    | the VPC                             |
| `network:ListSubnets`                                   | `GET /v1/subnets`                                                                                             | `subnet/*`                          |
| `network:CreateSubnet`                                  | `POST /v1/subnets`                                                                                            | the new subnet's CRN                |
| `network:GetSubnet`                                     | `GET /v1/subnets/{subnet_id}`                                                                                 | the subnet                          |
| `network:UpdateSubnet`                                  | `PATCH /v1/subnets/{subnet_id}`                                                                               | the subnet                          |
| `network:DeleteSubnet`                                  | `DELETE /v1/subnets/{subnet_id}`                                                                              | the subnet                          |
| `network:ListInterfaces`                                | `GET /v1/interfaces`                                                                                          | `interface/*`                       |
| `network:CreateInterface`                               | `POST /v1/interfaces`                                                                                         | the new interface's CRN             |
| `network:GetInterface`                                  | `GET /v1/interfaces/{interface_id}`                                                                           | the interface                       |
| `network:GetInterface`                                  | `GET /v1/interfaces/{interface_id}/addresses`, `GET /v1/interfaces/{interface_id}/prefixes`                   | the interface                       |
| `network:UpdateInterface`                               | `POST /v1/interfaces/{interface_id}/addresses`, `DELETE /v1/interfaces/{interface_id}/addresses/{address_id}` | the interface                       |
| `network:UpdateInterface`                               | `POST /v1/interfaces/{interface_id}/prefixes`, `DELETE /v1/interfaces/{interface_id}/prefixes/{prefix_id}`    | the interface                       |
| `network:UpdateInterface`                               | `PATCH /v1/interfaces/{interface_id}`                                                                         | the interface                       |
| `network:DeleteInterface`                               | `DELETE /v1/interfaces/{interface_id}`                                                                        | the interface                       |
| `network:ListInterfaceSecurityGroups`                   | `GET /v1/interfaces/{interface_id}/security-groups`                                                           | the interface                       |
| `network:SetInterfaceSecurityGroups`                    | `PUT /v1/interfaces/{interface_id}/security-groups`                                                           | the interface                       |
| `network:ListRouteTables`                               | `GET /v1/route-tables`                                                                                        | `route-table/*`                     |
| `network:CreateRouteTable`                              | `POST /v1/route-tables`                                                                                       | the new table's CRN                 |
| `network:GetRouteTable`                                 | `GET /v1/route-tables/{route_table_id}`                                                                       | the table                           |
| `network:UpdateRouteTable`                              | `PATCH /v1/route-tables/{route_table_id}`                                                                     | the table                           |
| `network:DeleteRouteTable`                              | `DELETE /v1/route-tables/{route_table_id}`                                                                    | the table                           |
| `network:ListRoutes`                                    | `GET /v1/route-tables/{route_table_id}/routes`                                                                | the **table**                       |
| `network:CreateRoute`                                   | `POST /v1/route-tables/{route_table_id}/routes`                                                               | the **table**                       |
| `network:GetRoute`                                      | `GET /v1/route-tables/{route_table_id}/routes/{route_id}`                                                     | the route                           |
| `network:UpdateRoute`                                   | `PATCH /v1/route-tables/{route_table_id}/routes/{route_id}`                                                   | the route                           |
| `network:DeleteRoute`                                   | `DELETE /v1/route-tables/{route_table_id}/routes/{route_id}`                                                  | the route                           |
| `network:ListSecurityGroups`                            | `GET /v1/security-groups`                                                                                     | `security-group/*`                  |
| `network:CreateSecurityGroup`                           | `POST /v1/security-groups`                                                                                    | the new group's CRN                 |
| `network:GetSecurityGroup`                              | `GET /v1/security-groups/{security_group_id}`                                                                 | the group                           |
| `network:UpdateSecurityGroup`                           | `PATCH /v1/security-groups/{security_group_id}`                                                               | the group                           |
| `network:DeleteSecurityGroup`                           | `DELETE /v1/security-groups/{security_group_id}`                                                              | the group                           |
| `network:ListSecurityGroupRules`                        | `GET /v1/security-groups/{security_group_id}/rules`                                                           | the **group**                       |
| `network:CreateSecurityGroupRule`                       | `POST /v1/security-groups/{security_group_id}/rules`                                                          | the **group**                       |
| `network:GetSecurityGroupRule`                          | `GET /v1/security-groups/{security_group_id}/rules/{rule_id}`                                                 | the **group**                       |
| `network:DeleteSecurityGroupRule`                       | `DELETE /v1/security-groups/{security_group_id}/rules/{rule_id}`                                              | the **group**                       |
| `network:ListFloatingIPs`                               | `GET /v1/floating-ips`                                                                                        | `floating-ip/*`                     |
| `network:CreateFloatingIP`                              | `POST /v1/floating-ips`                                                                                       | `floating-ip/*`                     |
| `network:GetFloatingIP`                                 | `GET /v1/floating-ips/{floating_ip_id}`                                                                       | the address                         |
| `network:UpdateFloatingIP`                              | `PATCH /v1/floating-ips/{floating_ip_id}`                                                                     | the address                         |
| `network:DeleteFloatingIP`                              | `DELETE /v1/floating-ips/{floating_ip_id}`                                                                    | the address                         |
| `network:AttachFloatingIP`                              | `POST /v1/floating-ips/{floating_ip_id}/attach`                                                               | the address                         |
| `network:DetachFloatingIP`                              | `POST /v1/floating-ips/{floating_ip_id}/detach`                                                               | the address                         |
| `network:ListInternetGateways`                          | `GET /v1/internet-gateways`                                                                                   | `internet-gateway/*`                |
| `network:CreateInternetGateway`                         | `POST /v1/internet-gateways`                                                                                  | the new gateway's CRN               |
| `network:GetInternetGateway`                            | `GET /v1/internet-gateways/{internet_gateway_id}`                                                             | the gateway                         |
| `network:GetInternetGateway` and `network:ListRoutes`   | `GET /v1/internet-gateways/{internet_gateway_id}/routes`                                                      | the gateway, then each owning table |
| `network:UpdateInternetGateway`                         | `PATCH /v1/internet-gateways/{internet_gateway_id}`                                                           | the gateway                         |
| `network:DeleteInternetGateway`                         | `DELETE /v1/internet-gateways/{internet_gateway_id}`                                                          | the gateway                         |
| `network:AttachInternetGateway`                         | `POST /v1/internet-gateways/{internet_gateway_id}/attach`                                                     | the gateway                         |
| `network:DetachInternetGateway`                         | `POST /v1/internet-gateways/{internet_gateway_id}/detach`                                                     | the gateway                         |
| `network:ListNATGateways`                               | `GET /v1/nat-gateways`                                                                                        | `nat-gateway/*`                     |
| `network:CreateNATGateway`                              | `POST /v1/nat-gateways`                                                                                       | the new gateway's CRN               |
| `network:GetNATGateway`                                 | `GET /v1/nat-gateways/{nat_gateway_id}`                                                                       | the gateway                         |
| `network:GetNATGateway` and `network:ListRoutes`        | `GET /v1/nat-gateways/{nat_gateway_id}/routes`                                                                | the gateway, then each owning table |
| `network:UpdateNATGateway`                              | `PATCH /v1/nat-gateways/{nat_gateway_id}`                                                                     | the gateway                         |
| `network:DeleteNATGateway`                              | `DELETE /v1/nat-gateways/{nat_gateway_id}`                                                                    | the gateway                         |
| `network:ListEgressOnlyGateways`                        | `GET /v1/egress-only-gateways`                                                                                | `egress-only-gateway/*`             |
| `network:CreateEgressOnlyGateway`                       | `POST /v1/egress-only-gateways`                                                                               | the new gateway's CRN               |
| `network:GetEgressOnlyGateway`                          | `GET /v1/egress-only-gateways/{egress_only_gateway_id}`                                                       | the gateway                         |
| `network:GetEgressOnlyGateway` and `network:ListRoutes` | `GET /v1/egress-only-gateways/{egress_only_gateway_id}/routes`                                                | the gateway, then each owning table |
| `network:UpdateEgressOnlyGateway`                       | `PATCH /v1/egress-only-gateways/{egress_only_gateway_id}`                                                     | the gateway                         |
| `network:DeleteEgressOnlyGateway`                       | `DELETE /v1/egress-only-gateways/{egress_only_gateway_id}`                                                    | the gateway                         |

Gateway route collections filter by route-table access. Reading the gateway
alone does not grant access to its routes: `network:ListRoutes` is checked
against each table's CRN and resource tags. Denied tables are omitted before
pagination, and the response includes only visible route-table identities.

## Creating is checked against the name you asked for

A create is authorized against the CRN of the resource *about to exist*, built
from the name in the request. So a naming convention is enforceable:

```json theme={null}
{
  "version": "2024-01-01",
  "statement": [
    {
      "effect": "allow",
      "actions": ["network:CreateSubnet", "network:CreateRouteTable"],
      "resources": [
        "crn:network:sa-saopaulo-1:my-account:vpc/team-a-*/subnet/*",
        "crn:network:sa-saopaulo-1:my-account:vpc/team-a-*/route-table/*"
      ]
    }
  ]
}
```

A request naming `team-b-web` is denied before anything is written. The request's
own `tags` are available as condition context on a create too, via
`basalt:RequestTag/<key>`, so you can require a team tag in the same statement.

<Warning>
  A CRN is account-scoped, but **subnet names are unique per VPC** and
  **interface names per subnet**. So `subnet/web` in a policy matches a subnet
  called `web` in *every* VPC in the account, not just the one you had in mind,
  and the same convention in two VPCs collapses into one policy scope.

  When you need to fence a single VPC's subnets, condition on a tag with
  `basalt:ResourceTag/<key>` rather than relying on the name.
</Warning>

## Top-level collections use wildcard permissions

Top-level resource lists are authorized against the collection wildcard — literally
`crn:network:<region>:<account>:vpc/*`, not against each row. A policy resource
of `vpc/prod-*` does not match that string, so narrowing a list by name pattern
does not restrict it, it **denies it entirely**:

| Policy resource                                   | `ListVPCs` | `GetVPC` on `vpc/prod-web` |
| ------------------------------------------------- | ---------- | -------------------------- |
| `crn:network:sa-saopaulo-1:my-account:vpc/*`      | allowed    | allowed                    |
| `crn:network:sa-saopaulo-1:my-account:vpc/prod-*` | **denied** | allowed                    |
| `crn:network:*:my-account:vpc/*`                  | allowed    | allowed                    |

So grant the wildcard for listing and scope the operations that *act* on a
resource. Listing tells a caller that something exists and nothing more.

## Sub-resources are governed by their parent — mostly

Security group rules have no CRN of their own. All four rule actions — list,
create, get and delete — are checked against the **parent group's** CRN with
the group's tags as context. Granting `network:CreateSecurityGroupRule` on a
group is therefore exactly as narrow as it reads, and a tag condition on the
group governs who may add rules to it.

Routes are the exception, and the split is worth knowing:

<Tabs>
  <Tab title="Checked against the table">
    `network:ListRoutes` and `network:CreateRoute` are authorized against the
    **route table's** CRN and tags. This is what lets you grant "may add routes
    to the private table" without granting it on `*`.
  </Tab>

  <Tab title="Checked against the route">
    `network:GetRoute`, `network:UpdateRoute` and `network:DeleteRoute` are
    authorized against the **route's own** `route/<id>` CRN, with the route's
    tags.

    Because a route's CRN is keyed by id, a policy cannot name one readably.
    Tag your routes if you need to distinguish them — a `deny` on
    `basalt:ResourceTag/protected` is the practical way to keep a default route
    from being deleted.
  </Tab>
</Tabs>

<Note>
  A grant to create routes on a table is close to a grant to change where that
  table's subnets send traffic. Someone who can add `0.0.0.0/0` to a private
  subnet's table has made it public — no gateway change and no security-group
  change required, because the gateway was already attached.
</Note>

## Interface membership is one action, not two

`network:SetInterfaceSecurityGroups` covers both attaching and detaching,
because the endpoint is a `PUT` that replaces the whole set. There is no
separate detach action to withhold: whoever can add a group to an interface can
remove every group from it, which leaves that interface dropping all traffic.

Grant it on the interfaces a caller is meant to manage, not on `interface/*`.

## Next

<CardGroup cols={2}>
  <Card title="Policies" icon="file-text" href="/iam/policies">
    Document structure, conditions, and how `deny` is evaluated.
  </Card>

  <Card title="Troubleshooting" icon="life-buoy" href="/networking/troubleshooting">
    What each refusal means, and the order things have to come apart in.
  </Card>
</CardGroup>
