# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

The NetFoundry API Gateway (MOP edge gateway). A Spring Cloud Gateway application (Spring Boot 3.4, Java 21, reactive/WebFlux) that fronts all internal MOP microservices. It routes incoming public API traffic to backend services and enforces **authentication** (not authorization) via a chain of global filters, converting external IdP tokens into internal NetFoundry-issued JWTs before proxying the request downstream.

`groupId io.netfoundry.gateway`, artifact `GatewayApplication`. Runs as an ECS service behind AWS Global Accelerator.

## Commands

All scripts wrap `./mvnw` and enforce Java 21 (`JAVA_HOME` must point at a 21 JDK). They also scrub AWS env vars and point the SDK at the repo-local `aws-config`/`aws-credentials` files.

- **Build / full install:** `./install.sh` (`mvn clean install`, profile `test`). Pass extra Maven args through, e.g. `./install.sh -DskipTests`.
- **Run locally:** `./run.sh` (`mvn spring-boot:run`, profile `development`). Requires a reachable Redis and the `mop.local` backend hosts (see `application.yaml` `netfoundry.hosts`).
- **Run the full test suite:** `./mvnw test -Dspring-boot.run.profiles=test` (or just `./install.sh`). Tests run under the `test` Spring profile and spin up an **embedded Redis** (`DataSourceTestConfiguration`) on the port from `test.redis.port`.
- **Run a single test class:** `./mvnw test -Dtest=WhitelistFilterTest`
- **Run a single test method:** `./mvnw test -Dtest=WhitelistFilterTest#methodName`
- **SonarCloud analysis:** `./sonar.sh` (requires `SONAR_TOKEN`; runs `mvn verify sonar:sonar`, project key `netfoundry_gateway`).
- **OWASP dependency check:** `./mvnw verify -Pdependency_check` (suppressions in `owasp/suppressed.xml`).

Surefire only picks up classes matching `*Test.java` — a class must end in `Test` to run.

## Architecture

### Request pipeline (global filters)

The core logic lives in `src/main/java/io/netfoundry/gateway/filters/`. Every filter implements Spring's `Ordered`; **lower `order` value runs earlier**. The pipeline authenticates the request and rewrites its `Authorization` header to an internal NF token, then Spring Cloud Gateway routes it downstream. Order (earliest → latest):

| order | filter | responsibility |
|------:|--------|----------------|
| -100 | `DiagnosticsFilter` | Populate MDC logging context (path, method, service, trace ids). Mirrors `DiagnosticsServletFilter` from `common-util-framework` but as a reactive `GlobalFilter`. |
| -90 | `AuditLoggingFilter` | Write to the dedicated `Http-Audit-Log` logger (routed to its own elastic index). |
| -85 | `ErrorTranslationFilter` | `WebFilter` decorating the response to translate/format error bodies (JSON/YAML). |
| -75 | `LogoutGatewayFilterFactory` | Route-scoped (not global) filter for `/identity/v1/logout`; invalidates the token's `TokenState`. |
| -50 | `WhitelistFilter` | Match request path/method against `authNotRequiredList` patterns. Marks matches **anonymous** (and may strip the auth header) so downstream filters skip auth. First filter to apply business logic. |
| -25 | `TokenStateFilter` | For bearer requests, reject if the token has been logged out (looked up in `TokenStateService` / Redis). |
| -5 | `BasicAuthValidationFilter` | Handle `Basic` auth: resolve client-id to an API Account (→ client-credentials grant at its IdP) or Service Identity (→ secret compared via Secrets Manager, NF token minted via Auth Service). Replaces the header with a bearer token. Discouraged/inefficient path. |
| -4 | `BearerTokenValidationFilter` | Handle `Bearer` tokens: dispatch to a `TokenValidationService` by JWT `iss` claim; **converts external IdP tokens into an internal NF token**. Authenticates only — does not authorize. |
| -1 | `AuthenticatedRequestFilter` | Final gate: allow only requests that are anonymous or carry an NF-issued token; otherwise 401. |

Filter state (anonymous flag, "handled", raw token) is passed between filters via exchange attributes — see `utils/FilterUtil`.

### Token validation (`tokenvalidation/`)

`TokenValidationService` is the strategy interface, keyed by `getIssuer()` (matched against the JWT `iss` claim). `BearerTokenValidationFilter` injects the full `Set` of them and builds an issuer→service map. Implementations:

- `NetfoundryTokenValidationService` — verifies NF-issued JWTs against the Auth Service JWKS (`/auth/jwks`); subject must be a valid MOP identity UUID.
- `Auth0TokenValidationService`, `AwsCognitoValidationService`, `NetworkAuthTokenValidationService` — external/other issuers.
- `OidcValidationService` — generic multi-IdP validator (separate from the `Set`, injected as `Optional` and gated on `netfoundry.gateway.auth.provider.oidc.enabled`; it dynamically resolves JWKS per token's issuer).

Each service resolves the external identity to an internal MOP `Identity` (via `IdentityService`, which calls the identity microservice) and applies an internal NF token for downstream use. Most services are toggled with `@ConditionalOnProperty` — check `application.yaml` `netfoundry.gateway.auth.provider.*` and `netfoundry.auth.provider.*`.

### Token state (`tokenstate/`)

`TokenStateService` tracks logout/invalidation state in **Redis via Redisson**, wrapped by a resilience4j circuit breaker in `utils/TokenCache`. Tokens are keyed by a hash (`utils/HashUtil`), never stored raw.

### Routing & config

- Routes and default filters are declared in `src/main/resources/application.yaml` under `spring.cloud.gateway.routes`. Each backend service (`core`, `identity`, `billing`, `auth`, `customer-connect`, `frontdoor`/zrok, etc.) is a route mapping a path prefix to a `netfoundry.hosts.*` URL. Most add a `RewritePath` to strip trailing slashes.
- **Anonymous endpoints** are configured, not coded: add a regex to `netfoundry.gateway.authNotRequiredList` in `application.yaml`. Set `passThrough: true` to keep the client's auth header; add `methods:` to restrict by HTTP verb. If a legitimate request 401s at `AuthenticatedRequestFilter`, this list is usually what needs updating.
- **Audience mapping** (`netfoundry.auth.audience`) maps a path's first segment to a token audience (e.g. `core` → `network`).
- `config/NetfoundryGatewayProperties` binds the `netfoundry.gateway.*` tree; `config/AuthConfiguration` builds the JWT consumers/validators; `config/FilterConfiguration` provides the tracing filter and JWKS resolver factory; `config/ServerConfiguration` handles server/HTTP setup.
- Profiles: `development` (local, via `run.sh` + `application-development.yaml`), `test` (CI/tests, embedded Redis), and prod defaults in `application.yaml`. Secrets/params come from AWS Secrets Manager & SSM Parameter Store at runtime.

## Deployment (`terraform/`)

Runs as an **ECS Fargate** service on the shared `mop-core` cluster. Never `terraform apply` without explaining changes and getting explicit per-change approval.

- **Environments are Terraform workspaces**, not separate dirs: `nf-sandbox`, `nf-staging-int`, `nf-prod-int`. Nearly every setting is a `terraform.workspace`-keyed map lookup in `variables.tf` / `locals.tf` (task count, subdomain, Spring profile label, Auth0 issuer, ALB log enablement, etc.). State is in S3 (`nf-mop-tf-state`, key `services/gatewayv2/terraform.tfstate`) with a DynamoDB lock table; the AWS provider assumes an `ops-mgmt-admin` role per account (`main.tf`).
- **Depends on `mop-core` remote state** (`imports.tf` reads `core/terraform.tfstate` and `core-failover-region/…`). VPC/subnets/security groups, the ECS cluster, Redis (primary + reader) addresses, ES host, the ACM wildcard cert, and the WAF ACL all come from mop-core outputs — this stack does not create them.
- **Traffic path:** Route53 A/alias `gateway.<subdomain>` → **Global Accelerator** (static IPs, `globalaccelerator.tf`) → **ALB** (HTTPS :443, TLS-1.2, wildcard ACM) → target group (IP target, health check `/actuator/health`, 200-299) → Fargate tasks on container port **8080**. Service registered in Cloud Map service discovery.
- **Task definition** is rendered from `templates/task-definition.json` (`ecs-service.tf`): 1024 CPU / 2048 MB, `desired_count` 1 (sandbox/staging) or 2 (prod), deployment circuit breaker with rollback. **This template is where the app's runtime config is injected** — `SPRING_PROFILES_ACTIVE`, the `NETFOUNDRY_GATEWAY_AUTH_PROVIDER_AUTH0_*` / `_COGNITO_*` env vars (relative-env override of `application.yaml`), Redis primary/failover addresses, ES metrics host, and Secrets-Manager-sourced ES creds. If an app config property needs a per-env value in prod, it's set here, not in `application.yaml`.
- **Image / release flow:** image is `…/gateway:<tag>` from ECR, where `<tag>` is read from an SSM parameter (`ssm.tf`, `ignore_changes` on value). Deploys go through `jenkinsfiles/serverless-api-deploy.jenkinsfile`: pick `ENVIRONMENT` + `IMAGE_TAG` → `record_ssm` writes the tag → `terraform` applies for that workspace → ECS health check. So a normal deploy is "bump the SSM tag + apply", not a code change.
- **WAF** (`waf.tf`): associates the ALB with the mop-core web ACL and defines regional rules — IP rate limit (3000 / 5 min), geo-block (IR/SY/CU/KP/SD + NK ipset), a manual-block ipset, and SQLi / XSS / size-constraint match rules.
- **IAM** (`iam-roles.tf`): task role assumes `ecs-tasks`, gets a copy of mop-core's internal-profile policy plus ECR pull / CloudWatch logs / `sns:Publish`. In sandbox only, it also mints an IAM user for local (`localhost`) MOP dev.
- **Failover region:** the failover ECS service/task/service-discovery exist (`ecs-service-failover.tf`), but the failover ALB, listener, and Global Accelerator failover endpoint are **commented out** and `failover_task_count` is 0 in every workspace — staged, not currently active.

## Other directories

- `jenkinsfiles/` — CI/CD (PR builds, develop pipeline, build & deploy). Builds are serverless/Bitbucket-driven (`BITBUCKET_*` vars in `Dockerfile`/`entrypoint.sh`); the build pipeline pushes the image to ECR, the deploy pipeline (above) promotes a tag.
- `taurus/` — Taurus/BlazeMeter API load & functional test scenarios (`config/*.yaml`, `scripts/*.groovy`) exercising the NF network API through the gateway.
- `Dockerfile` — runs the built `GatewayApplication-<version>-SNAPSHOT.jar` on Amazon Corretto 21 Alpine, port 8080.

## Conventions

- Lombok is used throughout (`@Slf4j`, `@Getter`, builders) — annotation processing must be enabled in the IDE.
- Reactive code (`Mono`/`Flux`, `ServerWebExchange`) — do not block; carry request state through exchange attributes, not thread locals (MDC is bridged via micrometer context-propagation).
- The gateway **authenticates, it does not authorize** — downstream services enforce authorization on the NF token. Don't add authorization logic here.
- Each filter's javadoc documents its position and contract; keep it accurate if you change `order`.
