# AWS Analysis

## ECS tasks with no modeled internet path

`exposed_internet` is only ever written as `true`, so a container without it may
be private, or may sit behind a path the exposure rules do not model. The
`aws_ecs_no_modeled_internet_path` analysis job records which tasks no modeled
internet path reaches, and which it checked and declined. It checks an
`AWSECSTask` when:

- the task's account was checked for unmodeled internet paths in the run that
  synced the task, whatever the check found;
- its task definition is in the graph, and so are its network interfaces when
  it uses `awsvpc` networking.

On every task it checks, and on the task's `AWSECSContainer` nodes, it sets
`no_modeled_internet_path_checked_at` to the run's update tag and
`no_modeled_internet_path` to `true` when all of these hold, and to `false`
otherwise:

- the account's check found no unmodeled internet paths;
- the task definition uses `awsvpc` networking. Bridge and host tasks share
  their instance's network, and a task definition without a network mode uses
  bridge;
- no network interface of the task has a public IPv4 address on any of its
  private IPs, an Elastic IP linked to it, or an IPv6 address. The Elastic IP
  sync can link one in a run that does not sync the interfaces, which is when
  their public IPv4 addresses are refreshed;
- every `AWSLoadBalancerV2` that reaches the task is `internal`, its account
  was checked at or after the load balancer was synced, with none found, and no
  load balancer targets one of that account's load balancer interfaces, as
  described below. That covers IP targets, a target group on the task's
  service, and a load balancer chained in front of either through an `alb`
  target group, such as an internet-facing NLB that forwards to an internal
  ALB;
- no container of the task is marked `exposed_internet`.

`true` describes the graph, not the network: no internet path that Cartography
models reaches the task. The paths it does not see are listed below. `false`
means the job declined, not that the task is exposed: a modeled path may reach
it, or something in front of it could not be ruled out, such as a load balancer
in an account that was not checked. A task the job cannot check gets neither
property.

The job runs at the end of every AWS sync, whatever modules were synced. It
checks every task in the graph, not only the tasks this run synced, because a
load balancer in one account can reach a task in another, accounts can be
synced by separate runs, and a run that syncs only some modules can change what
an earlier result relied on. Its properties are replaced at the end of the run
instead of being cleared first, so a run that fails part-way leaves the previous
values in place. A run that completes removes them from every task it did not
check, so a withdrawn result does not linger.

A result is current when `no_modeled_internet_path_checked_at` is at least the
node's `lastupdated`, and its value, `true` or `false`, is then authoritative.
The two are equal when the same run synced the task and checked it. A later run
that checks a task it did not sync writes its own, newer tag. Without a current
result, the task has not been checked since it was last synced. After a failed
run, a task that the run synced keeps the older tag. A failed run does not
re-check the tasks it did not sync either, so their results can predate what it
changed, such as a load balancer in another account or an entry point in their
own account. The next successful run corrects them, whichever accounts it
syncs, because a run removes an account's check before it syncs any of the
check's inputs, as described below.

```cypher
MATCH (account:AWSAccount)-[:RESOURCE]->(task:AWSECSTask)
OPTIONAL MATCH (container:AWSECSContainer)-[:WORKLOAD_PARENT]->(task)
RETURN account.id, account.unmodeled_internet_paths,
       task.arn, container.name, container.exposed_internet,
       CASE WHEN task.no_modeled_internet_path_checked_at >= task.lastupdated
            THEN task.no_modeled_internet_path END AS no_modeled_internet_path
ORDER BY account.id, task.arn
```

`no_modeled_internet_path` is `null` there for a task that has not been checked.

## Unmodeled internet paths

Some internet entry points terminate on private addresses, so the internal
load balancer or task behind them looks private from the graph. The
`aws_unmodeled_internet_paths` job checks one account at the end of its sync and
records on the `AWSAccount`:

- `unmodeled_internet_paths`: up to 10 IDs of the entry points it found. Unset
  when there are none.
- `unmodeled_internet_path_count`: how many it found, `0` when there are none.
- `unmodeled_internet_paths_checked_at`: the update tag of the run that checked
  the account.

It finds:

- CloudFront distributions with a VPC origin;
- API Gateway REST integrations over a VPC link;
- every API Gateway v2 (HTTP or WebSocket) API, because v2 VPC link
  integrations are not ingested.

The IDs are the distribution ARN, the integration ID
(`apiId/resourceId/httpMethod`) and the v2 API ID.

The check also vouches for the account's load balancers, their IP targets and
its network interfaces, which the ECS analysis reads. It runs only
when `cloudfront`, `apigateway`, `apigatewayv2`, `ec2:load_balancer_v2`,
`ec2:load_balancer_v2:expose`, `ec2:network_interface` and `ec2:subnet` were all
synced. IP targets resolve through the VPC of the target's subnet, which only
the subnet sync records. A run that syncs any of them removes the account's
previous result before the account's syncs start, and writes a new one after
them only when it synced all of them. A run that syncs some of them, or that
fails part-way, leaves the account unchecked, so an older check never vouches
for newer data.

The ECS job checks a task only when its account was checked in the run that
synced the task, and sets `true` only when that check's count is `0`. An account
counts as clear for a load balancer only when its count is `0` and its check is
at least as recent as the load balancer. An account that was never checked, or
whose check is older, counts as unknown, and the tasks behind its load balancers
get `false`.

## Load balancers in front of load balancer interfaces

A load balancer can list another load balancer's private IPs as IP targets. This
is the static IP pattern for an ALB, where an NLB's IP target group lists the
private IPs of the ALB's interfaces. The graph does not link a load balancer's
interfaces to it, so the ALB and its targets would look private. The ECS
analysis therefore treats every `internal` load balancer in an account
as one that may face the internet when a load balancer interface of that
account (an `AWSNetworkInterface` with `requester_id` `amazon-elb`) is reached,
as an IP target or through an `alb` target group chained in front of one, by a
load balancer that:

- is not `internal`, or
- belongs to another account, whatever its scheme. Judging an internal one
  there would take the same rule for its own account's interfaces, and so on.

An `internal` one in the same account does not count: whatever reaches it is one
of the account's entry points, one of its load balancer interfaces, or a load
balancer chained in front of it, which the rules above already cover.

The rule spans accounts, so it is evaluated each time the ECS job runs, not in
the account check, and nothing about it is recorded on the account. A load
balancer synced with another account counts as soon as its IP target is linked,
whichever account synced first.

## Paths the checks do not see

The checks read the graph, so they only see what the syncs loaded.

- Global Accelerator, App Runner VPC ingress, VPC Lattice and Verified Access
  are not ingested.
- PrivateLink endpoint services, which let other AWS accounts, and anything
  those accounts expose, reach an internal NLB.
- A CloudFront VPC origin or API Gateway VPC link that reaches a load balancer
  or task in another account, for example through a shared VPC. Only the entry
  point's own account is checked.
- Load balancers in accounts that are not synced into the same graph, and IP
  targets that do not resolve to exactly one private IP in the graph.
- A load balancer in another account whose target is newer than one of the two
  accounts' syncs. An IP target is linked only when the load balancer's account
  syncs after the target interface, a task's or another load balancer's, is in
  the graph, and a target group is linked to a service only when the task's
  account syncs after the target group is in the graph. When both accounts sync
  in the same run, one after the other, a task whose service is registered with
  the target group is caught by one of the two links. A task that is an IP
  target in some other way, such as a standalone task registered by hand, and a
  load balancer interface that is one, are missed when the load balancer's
  account syncs first, until that account syncs again.
- Resources a sync skipped. Cartography skips a region, without failing the
  run, when an API call is denied or times out, and the module's cleanup then
  removes what it had loaded from there. A missing CloudFront, API Gateway,
  ELBv2 or EC2 read permission, or a timeout, can hide an entry point or a load
  balancer and leave the account looking clear. After a transient ELBv2 or API
  Gateway v2 failure in a region, that service's cleanup is skipped for the
  whole account: the previous load balancers or APIs in every region of the
  account are kept, and new ones in the failed region are missing.
- Application-layer forwarding, such as a proxy task that relays internet
  traffic to another service.
