Metadata-Version: 2.4
Name: attackmap-analyzer-terraform
Version: 0.2.0
Summary: Terraform / HCL infrastructure-as-code analyzer plugin for AttackMap (AWS, Azure, GCP resources; IAM wildcards; open security groups; secret resources).
Author: AttackMap Contributors
Author-email: Matthew Davis <mlaify@mlaify.io>
Maintainer-email: Matthew Davis <mlaify@mlaify.io>
License-Expression: MIT
Project-URL: Homepage, https://github.com/mlaify/attackmap-analyzer-terraform
Project-URL: Repository, https://github.com/mlaify/attackmap-analyzer-terraform
Project-URL: Issues, https://github.com/mlaify/attackmap-analyzer-terraform/issues
Project-URL: Changelog, https://github.com/mlaify/attackmap-analyzer-terraform/blob/main/CHANGELOG.md
Project-URL: AttackMap core, https://github.com/mlaify/AttackMap
Keywords: security,static-analysis,appsec,defensive-security,attackmap,attackmap-plugin
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic>=2.7.0
Provides-Extra: core
Requires-Dist: attackmap>=0.6.0; extra == "core"
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: build>=1.2.0; extra == "dev"
Requires-Dist: attackmap>=0.6.0; extra == "dev"
Dynamic: license-file

# attackmap-analyzer-terraform

> [!IMPORTANT]
> **Active development, slow pace.** AttackMap is under active development, but
> progress may be slow until more contributors or co-maintainers join. Help is
> very welcome with the core engine, an analyzer, the macOS app, or the docs —
> see [CONTRIBUTING.md](CONTRIBUTING.md) or open an issue on
> [mlaify/AttackMap](https://github.com/mlaify/AttackMap/issues) to say hello.
> Security reports are still welcome at [security@mlaify.io](mailto:security@mlaify.io).

Terraform / HCL infrastructure-as-code analyzer for [AttackMap](https://github.com/mlaify/AttackMap).

This analyzer is shaped differently from language analyzers — Terraform doesn't have routes in the application sense. Instead, it extracts:

- **Public ingress** — security groups (`ingress` blocks only; open egress is ignored) and inbound firewall rules with `0.0.0.0/0` / `::/0`, Lambda Function URLs with no auth, API Gateway methods with `authorization = "NONE"`, S3 buckets with public ACLs (inline or `aws_s3_bucket_acl`), public bucket policies or disabled public-access blocks → `entrypoint_hints`
- **Asset inventory** — S3 buckets, Azure storage accounts, GCS buckets, KMS keys, Cognito user pools → `service_hints`
- **Databases** — `aws_db_instance` and `aws_rds_cluster` (engine-aware: postgres / mysql / oracle / sqlserver / mariadb), `aws_dynamodb_table`, `aws_elasticache_*`, `aws_documentdb_*`, Azure PostgreSQL/MySQL/CosmosDB, GCP Cloud SQL → `database_hints`
- **Secrets** — `aws_secretsmanager_secret`, `aws_ssm_parameter` (SecureString), `azurerm_key_vault`, plus `variable` blocks marked `sensitive = true` or with secret-shaped names (`*secret*`, `*token*`, `*password*`, `*key*`), and secret-shaped string literals in `.tfvars` (`kind="hardcoded"`) → `secret_hints`. A secret variable with a literal string `default` is `kind="hardcoded"` (the value is never echoed); a bare declaration keeps the default kind.
- **IAM blast radius** — Allow statements with `Action "*"` (0.7), `Resource "*"` (0.5) or unconditioned `Principal "*"` (0.7) in `aws_iam_*_policy`, `aws_iam_role` trust/inline policies and `data "aws_iam_policy_document"` → `auth_hints`
- **Cognito** — `aws_cognito_user_pool` → `auth_hints`
- **Modules** — `module "x" { source = ... }` → `service_hints` keyed `module:x`
- **Misconfigurations** → `cloud_misconfigs` (AttackMap's `CloudMisconfig` record, which core turns into findings with CWE/OWASP ids). Each record names the resource, carries its `for_each`/`count` and, for resources inside a local module, the `module.<name> (file:line)` blocks that call it:
  - `iac_module_unpinned`: git module sources without `?ref=` or pinned to a branch (`main`, `master`, `develop`, ...), and registry modules without `version`. A tag (`?ref=v1.2.3`) or commit SHA is pinned.
  - `public_network_access`: `associate_public_ip_address = true` (`aws_instance`, launch configurations and templates), `publicly_accessible = true` RDS, and `public_network_access_enabled = true` on any `azurerm_*` resource.
  - `public_cluster_endpoint`: `aws_eks_cluster` whose API endpoint is public and open to `0.0.0.0/0` (both are the defaults, reported with lower confidence when left implicit).
  - `plaintext_listener`: `aws_lb_listener` on `HTTP` without a redirect default action, unless its load balancer is `internal = true`.
  - `api_no_authorizer`: API Gateway v1 methods with `authorization = "NONE"` and no API key, and v2 HTTP routes with `authorization_type = "NONE"` or none at all (the default). `OPTIONS` (CORS preflight) is skipped.
  - `unencrypted_storage`: `storage_encrypted = false` on `aws_db_instance` / `aws_rds_cluster`, and `aws_db_instance` without it (defaults to false; replicas, snapshot restores and `kms_key_id` are skipped).
  - `public_data_access`: public S3 ACLs and policies, disabled public-access blocks, OpenSearch / Elasticsearch `access_policies` open to `Principal "*"`, GCS buckets granted to `allUsers` / `allAuthenticatedUsers` (IAM, ACLs, `predefined_acl = "publicRead"`), `allow_nested_items_to_be_public = true` and public `azurerm_storage_container`s.
  - `public_invoker`: Cloud Run / Cloud Functions invoker roles granted to `allUsers` / `allAuthenticatedUsers`, and Lambda function URLs with `authorization_type = "NONE"`.
  - kubernetes provider: privileged containers, `allow_privilege_escalation = true`, `host_network` / `host_pid` / `host_ipc`, `host_path` volumes (high for the runtime socket, `/`, `/etc`, `/proc`, `/var/lib/kubelet`, ...), `cluster-admin` or `system:authenticated` bindings, roles with `*` / `escalate` / `bind` / `impersonate` / `pods/exec` / Secret read, and `LoadBalancer` / `NodePort` services without `load_balancer_source_ranges`.
  - Only literal values are judged: an attribute set from `var.x` or another reference is skipped.
- **API Gateway v2 routes** — `aws_apigatewayv2_route` with `route_key = "POST /charges"` → actual `Route` entries
- **API Gateway v1 routes** — `aws_api_gateway_method` on an `aws_api_gateway_resource` chain (`parent_id` / `path_part` joined back to `root_resource_id`, across files) → `Route` entries
- **Lambda function URLs** — `aws_lambda_function_url` → an `ANY /` `Route`
- **Route auth (AttackMap#256)** — every route carries `auth` / `guards` / `guard_evidence`. `authorization` / `authorization_type` = `AWS_IAM`, `COGNITO_USER_POOLS`, `CUSTOM` or `JWT` (or v1 `api_key_required = true`) → `required`. `"NONE"` written out → `anonymous`. Omitted or computed (`var.x`) → `unknown`.

All emissions populate AttackMap's Signal v2 fields (line numbers + evidence snippets) so downstream insights can cite `infra/main.tf:NN`.

## Install

```bash
uv tool install attackmap --with attackmap-analyzer-terraform \
  --index mlaify=https://pkg.mlaify.io/pypi/simple/ --index-strategy first-index
# or from source:
pip install git+https://github.com/mlaify/attackmap-analyzer-terraform.git
```

The analyzer is auto-discovered by AttackMap via the `attackmap.analyzers` entry-point group.

## Usage with AttackMap

```bash
# Auto-discovered when installed:
attackmap analyze /path/to/terraform/repo

# Or invoke explicitly:
attackmap analyze /path/to/terraform/repo --module terraform
```

## Detection

`detect()` returns true when any `.tf`, `.tf.json`, or `.tfvars` file is present in the tree, ignoring `.terraform/` and AttackMap's shared skip list (`.git/`, `node_modules/`, `vendor/`, `build/`, `dist/`, `out/`, `target/`, ...). Skip directories are matched only *inside* the repo, so a checkout under e.g. `/build/...` is still scanned. Files are walked with `attackmap.sdk.iter_repo_files`, which does not follow symlinks out of the repo.

## HCL block parsing

The analyzer parses HCL block bodies via brace-depth counting (with string-literal awareness so `"{" inside strings"` doesn't throw off the counter). Nested blocks like multiple `ingress { }` blocks inside a security group are handled correctly. **Variable interpolation** (`${var.foo}`, `aws_iam_role.app.id`) is **not resolved** — string literals and direct attribute values are what get extracted.

## Coverage notes

- **AWS** is the most thoroughly covered provider. Azure and GCP have basic coverage (NSG/firewall open ingress, Storage Account / GCS bucket as service hints, PostgreSQL/MySQL/CosmosDB/CloudSQL as databases) — extend per resource type as needed.
- **IAM wildcard detection** matches JSON-shaped (`"Action": "*"`, heredoc or `.tf.json` string) and HCL-shaped (`Action = "*"` in `jsonencode`, `actions = ["*"]` in `aws_iam_policy_document`) policies. A `policy = data.aws_iam_policy_document.x.json` reference isn't followed; the data source is reported under its own name.
- **`.tf.json`** files get the same analysis as `.tf`. **`.tfvars`** files are checked for secret-shaped string literals (reported as `kind="hardcoded"` with the value redacted).
- **API Gateway v1** methods become routes when their `resource_id` resolves through literal `aws_api_gateway_resource` blocks to a `root_resource_id`. Methods on resources from modules or with interpolated `path_part`s only emit entrypoint hints. OpenAPI `body`-defined REST APIs aren't parsed.
- **Lambda Function URLs** with `authorization_type = "NONE"` are flagged as open entrypoints. IAM-authorized URLs are emitted with the regular `lambda_url:` prefix.
- **Database `publicly_accessible = true`** on `aws_db_instance` produces a separate `rds_publicly_accessible:` entrypoint hint in addition to the standard database hint.

## License

MIT
