|
| 1 | +# Meta |
| 2 | +[meta]: #meta |
| 3 | +- Name: App-to-App mTLS via GoRouter |
| 4 | +- Start Date: 2026-02-16 |
| 5 | +- Author(s): @rkoster |
| 6 | +- Status: Draft |
| 7 | +- RFC Pull Request: [community#1438](https://github.com/cloudfoundry/community/pull/1438) |
| 8 | + |
| 9 | + |
| 10 | +## Summary |
| 11 | + |
| 12 | +Enable authenticated and authorized app-to-app communication via GoRouter using mutual TLS (mTLS). Applications connect to a shared internal domain (e.g., `apps.mtls.internal`), resolvable only from within CF, where GoRouter requires client certificates, validates caller identity, and enforces per-route access control before forwarding requests. |
| 13 | + |
| 14 | +This follows the same default-deny model as container-to-container network policies: all traffic is blocked unless explicitly allowed. |
| 15 | + |
| 16 | + |
| 17 | +## Problem |
| 18 | + |
| 19 | +Cloud Foundry applications can communicate via external routes (through GoRouter) or container-to-container networking (direct). Neither option provides authenticated app-to-app communication with platform-enforced authorization: |
| 20 | + |
| 21 | +- **External routes**: Traffic leaves the VPC to reach the load balancer, adding latency and cost. GoRouter's client certificate settings are global, so enabling strict mTLS breaks external clients. |
| 22 | +- **C2C networking**: Requires `network.write` permission (often unavailable to developers), lacks load balancing and observability, and has no identity forwarding. |
| 23 | + |
| 24 | +**The gap**: There is no way for applications to communicate securely through GoRouter where: |
| 25 | +- Only CF applications can connect (mTLS with instance identity) |
| 26 | +- Traffic stays internal (no load balancer round-trip) |
| 27 | +- The platform enforces which apps can call which routes |
| 28 | +- Standard GoRouter features work (load balancing, retries, observability) |
| 29 | + |
| 30 | +Authentication alone is insufficient. Without authorization enforcement, any authenticated app could access any route on the mTLS domain, defeating the purpose of platform-enforced security. |
| 31 | + |
| 32 | + |
| 33 | +## Proposal |
| 34 | + |
| 35 | +### Architecture Overview |
| 36 | + |
| 37 | +```mermaid |
| 38 | +flowchart LR |
| 39 | + subgraph "App A Container" |
| 40 | + AppA["App A"] |
| 41 | + ProxyA["Envoy<br/>(egress proxy)"] |
| 42 | + end |
| 43 | + |
| 44 | + subgraph "GoRouter" |
| 45 | + GR["1. Validate cert<br/>(Instance Identity CA)"] |
| 46 | + Auth["2. Check allowed_sources"] |
| 47 | + end |
| 48 | + |
| 49 | + subgraph "App B Container" |
| 50 | + ProxyB["Envoy<br/>(validates GoRouter cert)"] |
| 51 | + AppB["App B"] |
| 52 | + end |
| 53 | + |
| 54 | + AppA -->|"HTTP"| ProxyA |
| 55 | + ProxyA -->|"mTLS<br/>(instance cert)"| GR |
| 56 | + GR --> Auth |
| 57 | + Auth -->|"authorized<br/>mTLS<br/>(GoRouter cert)"| ProxyB |
| 58 | + Auth -.->|"403 Forbidden"| ProxyA |
| 59 | + ProxyB -->|"HTTP + XFCC"| AppB |
| 60 | +``` |
| 61 | + |
| 62 | +The solution has two core components that must ship together: |
| 63 | + |
| 64 | +- **Phase 1a (mTLS Infrastructure)**: GoRouter requires and validates client certificates for the mTLS domain, forwarding caller identity via the XFCC header. |
| 65 | +- **Phase 1b (Authorization Enforcement)**: GoRouter enforces per-route access control. Routes are blocked by default unless `allowed_sources` explicitly permits the caller. |
| 66 | + |
| 67 | +An optional enhancement simplifies client adoption: |
| 68 | + |
| 69 | +- **Phase 2 (Egress HTTP Proxy)**: Sidecar proxy automatically injects instance identity certificates, so apps don't need TLS configuration. |
| 70 | + |
| 71 | +### Phase 1a: mTLS Infrastructure |
| 72 | + |
| 73 | +GoRouter gains the ability to require client certificates for specific domains while leaving other domains unaffected. |
| 74 | + |
| 75 | +**How it works:** |
| 76 | +1. Operator configures an internal domain (e.g., `apps.mtls.internal`) with mTLS requirements |
| 77 | +2. BOSH DNS resolves `*.apps.mtls.internal` to GoRouter instances |
| 78 | +3. Applications map routes to this domain like any shared domain |
| 79 | +4. When a client connects, GoRouter: |
| 80 | + - Requires a client certificate |
| 81 | + - Validates it against the Instance Identity CA |
| 82 | + - Sets the XFCC header with the certificate details |
| 83 | + - Proceeds to authorization check (Phase 1b) |
| 84 | + |
| 85 | +**Security settings** for the mTLS domain use the strictest configuration: |
| 86 | +- `client_cert_validation: require`: clients must present a certificate |
| 87 | +- `only_trust_client_ca_certs: true`: only Instance Identity CA is trusted |
| 88 | +- `forwarded_client_cert: sanitize_set`: XFCC header cannot be spoofed |
| 89 | + |
| 90 | +### Phase 1b: Authorization Enforcement |
| 91 | + |
| 92 | +GoRouter enforces access control at the routing layer using a default-deny model, matching the design of container-to-container network policies. |
| 93 | + |
| 94 | +**How it works:** |
| 95 | +1. Route owner specifies `allowed_sources` in the route configuration |
| 96 | +2. When a request arrives on an mTLS domain, GoRouter: |
| 97 | + - Extracts the caller's identity from the client certificate (app GUID, space GUID, org GUID) |
| 98 | + - Checks if the identity matches any `allowed_sources` rule |
| 99 | + - Returns `403 Forbidden` if not authorized (default-deny) |
| 100 | + - Forwards the request with XFCC header if authorized |
| 101 | + |
| 102 | +**Route configuration example:** |
| 103 | +```yaml |
| 104 | +applications: |
| 105 | +- name: backend-api |
| 106 | + routes: |
| 107 | + - route: backend.apps.mtls.internal |
| 108 | + options: |
| 109 | + allowed_sources: |
| 110 | + apps: ["frontend-app-guid"] |
| 111 | + spaces: ["trusted-space-guid"] |
| 112 | +``` |
| 113 | +
|
| 114 | +This builds on the route options framework from [RFC-0027: Generic Per-Route Features](rfc-0027-generic-per-route-features.md). Phase 1b depends on RFC-0027 being implemented first. |
| 115 | +
|
| 116 | +### Implementation References |
| 117 | +
|
| 118 | +| Component | Reference | |
| 119 | +|-----------|-----------| |
| 120 | +| GoRouter TLS config | [`routing-release/.../config.go`](https://github.com/cloudfoundry/routing-release/blob/develop/src/code.cloudfoundry.org/gorouter/config/config.go) | |
| 121 | +| GoRouter BOSH spec | [`routing-release/jobs/gorouter/spec`](https://github.com/cloudfoundry/routing-release/blob/develop/jobs/gorouter/spec) | |
| 122 | +| RFC-0027 route options | [`toc/rfc/rfc-0027-generic-per-route-features.md`](rfc-0027-generic-per-route-features.md) | |
| 123 | +| Cloud Controller routes | [`cloud_controller_ng/.../route.rb`](https://github.com/cloudfoundry/cloud_controller_ng/blob/main/app/models/runtime/route.rb) | |
| 124 | + |
| 125 | + |
| 126 | +## Security Model |
| 127 | + |
| 128 | +Two layers of mTLS enforcement ensure only authorized traffic reaches applications: |
| 129 | + |
| 130 | +| Layer | Validates | Trusts | |
| 131 | +|-------|-----------|--------| |
| 132 | +| Client → GoRouter | App's instance identity cert | Instance Identity CA | |
| 133 | +| GoRouter → Backend | GoRouter's backend cert | GoRouter Backend CA | |
| 134 | + |
| 135 | +This ensures: |
| 136 | +- Only CF application instances can connect to mTLS routes |
| 137 | +- Only GoRouter can connect to application backends |
| 138 | +- Applications cannot bypass GoRouter |
| 139 | + |
| 140 | + |
| 141 | +## Release Criteria |
| 142 | + |
| 143 | +**Phase 1a and Phase 1b are co-requisites and must be released together.** |
| 144 | + |
| 145 | +Deploying Phase 1a without Phase 1b would leave all mTLS routes accessible to any authenticated app, violating the default-deny security model. This RFC is not production-ready without both phases. |
| 146 | + |
| 147 | +Phase 1b depends on [RFC-0027: Generic Per-Route Features](rfc-0027-generic-per-route-features.md) being implemented first. |
| 148 | + |
| 149 | +This aligns with the container-to-container networking model where network policies enforce default-deny before traffic is allowed between apps. |
| 150 | + |
| 151 | + |
| 152 | +## Optional Enhancements |
| 153 | + |
| 154 | +### Phase 2: Egress HTTP Proxy |
| 155 | + |
| 156 | +To simplify client adoption, add an HTTP proxy to the application sidecar that automatically handles mTLS. |
| 157 | + |
| 158 | +**How it works:** |
| 159 | +1. Diego configures an egress proxy (Envoy) listening on `127.0.0.1:8888` |
| 160 | +2. The proxy is configured to intercept requests to `*.apps.mtls.internal` |
| 161 | +3. For matching requests, the proxy: |
| 162 | + - Upgrades the connection to TLS |
| 163 | + - Presents the application's instance identity certificate |
| 164 | + - Forwards the request to GoRouter |
| 165 | + |
| 166 | +**Application usage:** |
| 167 | +```bash |
| 168 | +# Client app sets HTTP_PROXY for the internal domain |
| 169 | +export HTTP_PROXY=http://127.0.0.1:8888 |
| 170 | +export NO_PROXY=external-api.example.com |
| 171 | +
|
| 172 | +# Plain HTTP request, proxy handles mTLS automatically |
| 173 | +curl http://myservice.apps.mtls.internal/api |
| 174 | +``` |
| 175 | + |
| 176 | +This eliminates the need for applications to load certificates and configure TLS clients. |
| 177 | + |
| 178 | +**Implementation references:** |
| 179 | +- Diego Envoy proxy: [`diego-release/.../envoy-builder`](https://github.com/cloudfoundry/diego-release/tree/develop/src/code.cloudfoundry.org/envoy-builder) |
| 180 | +- Instance identity certs: [`diego-release/docs/050-app-instance-identity.md`](https://github.com/cloudfoundry/diego-release/blob/develop/docs/050-app-instance-identity.md) |
| 181 | + |
| 182 | + |
| 183 | +## Key Repositories |
| 184 | + |
| 185 | +| Repository | Changes | |
| 186 | +|------------|---------| |
| 187 | +| [routing-release](https://github.com/cloudfoundry/routing-release) | Phase 1a: `mtls_domains` config; Phase 1b: `allowed_sources` enforcement | |
| 188 | +| [capi-release](https://github.com/cloudfoundry/capi-release) | Phase 1b: `allowed_sources` route option validation | |
| 189 | +| [diego-release](https://github.com/cloudfoundry/diego-release) | Phase 2: Egress proxy configuration | |
| 190 | +| [cf-deployment](https://github.com/cloudfoundry/cf-deployment) | Ops-file for enabling mTLS app routing | |
| 191 | + |
| 192 | + |
| 193 | +## References |
| 194 | + |
| 195 | +- [RFC-0027: Generic Per-Route Features](rfc-0027-generic-per-route-features.md) |
| 196 | +- [Container-to-Container Networking](https://docs.cloudfoundry.org/concepts/understand-cf-networking.html) |
| 197 | +- [Diego Instance Identity Documentation](https://github.com/cloudfoundry/diego-release/blob/develop/docs/050-app-instance-identity.md) |
| 198 | +- [GoRouter Client Certificate Configuration](https://github.com/cloudfoundry/routing-release/blob/develop/jobs/gorouter/spec) |
0 commit comments