Skip to content

Commit 8f3900a

Browse files
committed
RFC: App-to-App mTLS via GoRouter
Enable authenticated and authorized app-to-app communication via GoRouter using mutual TLS (mTLS). Applications connect to a shared internal domain (apps.mtls.internal), where GoRouter validates client certificates and enforces per-route access control using a default-deny model. Key features: - Phase 1a: Domain-specific mTLS in GoRouter (validates instance identity) - Phase 1b: Authorization enforcement via allowed_sources route option - Phase 2 (optional): Egress HTTP proxy for simplified client adoption Depends on RFC-0027 (Generic Per-Route Features) for route options support.
1 parent 8d676db commit 8f3900a

1 file changed

Lines changed: 217 additions & 0 deletions

File tree

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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 examples:**
103+
104+
Platform-enforced authorization with explicit allowlist:
105+
```yaml
106+
applications:
107+
- name: backend-api
108+
routes:
109+
- route: backend.apps.mtls.internal
110+
options:
111+
allowed_sources:
112+
apps: ["frontend-app-guid"]
113+
spaces: ["trusted-space-guid"]
114+
```
115+
116+
App-delegated authorization (any authenticated app allowed):
117+
```yaml
118+
applications:
119+
- name: autoscaler-api
120+
routes:
121+
- route: autoscaler.apps.mtls.internal
122+
options:
123+
allowed_sources:
124+
any: true
125+
```
126+
127+
When `any: true` is set, GoRouter allows any request with a valid instance identity certificate. The app receives the XFCC header and performs its own authorization checks. This is useful when authorization depends on dynamic information (e.g., service bindings) that cannot be determined at route creation time.
128+
129+
**Validation rules:**
130+
- `any: true` is mutually exclusive with `apps`, `spaces`, and `orgs`
131+
- If `any` is not set, at least one of `apps`, `spaces`, or `orgs` must be specified (default-deny)
132+
133+
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.
134+
135+
### Implementation References
136+
137+
| Component | Reference |
138+
|-----------|-----------|
139+
| GoRouter TLS config | [`routing-release/.../config.go`](https://github.com/cloudfoundry/routing-release/blob/develop/src/code.cloudfoundry.org/gorouter/config/config.go) |
140+
| GoRouter BOSH spec | [`routing-release/jobs/gorouter/spec`](https://github.com/cloudfoundry/routing-release/blob/develop/jobs/gorouter/spec) |
141+
| RFC-0027 route options | [`toc/rfc/rfc-0027-generic-per-route-features.md`](rfc-0027-generic-per-route-features.md) |
142+
| Cloud Controller routes | [`cloud_controller_ng/.../route.rb`](https://github.com/cloudfoundry/cloud_controller_ng/blob/main/app/models/runtime/route.rb) |
143+
144+
145+
## Security Model
146+
147+
Two layers of mTLS enforcement ensure only authorized traffic reaches applications:
148+
149+
| Layer | Validates | Trusts |
150+
|-------|-----------|--------|
151+
| Client → GoRouter | App's instance identity cert | Instance Identity CA |
152+
| GoRouter → Backend | GoRouter's backend cert | GoRouter Backend CA |
153+
154+
This ensures:
155+
- Only CF application instances can connect to mTLS routes
156+
- Only GoRouter can connect to application backends
157+
- Applications cannot bypass GoRouter
158+
159+
160+
## Release Criteria
161+
162+
**Phase 1a and Phase 1b are co-requisites and must be released together.**
163+
164+
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.
165+
166+
Phase 1b depends on [RFC-0027: Generic Per-Route Features](rfc-0027-generic-per-route-features.md) being implemented first.
167+
168+
This aligns with the container-to-container networking model where network policies enforce default-deny before traffic is allowed between apps.
169+
170+
171+
## Optional Enhancements
172+
173+
### Phase 2: Egress HTTP Proxy
174+
175+
To simplify client adoption, add an HTTP proxy to the application sidecar that automatically handles mTLS.
176+
177+
**How it works:**
178+
1. Diego configures an egress proxy (Envoy) listening on `127.0.0.1:8888`
179+
2. The proxy is configured to intercept requests to `*.apps.mtls.internal`
180+
3. For matching requests, the proxy:
181+
- Upgrades the connection to TLS
182+
- Presents the application's instance identity certificate
183+
- Forwards the request to GoRouter
184+
185+
**Application usage:**
186+
```bash
187+
# Client app sets HTTP_PROXY for the internal domain
188+
export HTTP_PROXY=http://127.0.0.1:8888
189+
export NO_PROXY=external-api.example.com
190+
191+
# Plain HTTP request, proxy handles mTLS automatically
192+
curl http://myservice.apps.mtls.internal/api
193+
```
194+
195+
This eliminates the need for applications to load certificates and configure TLS clients.
196+
197+
**Implementation references:**
198+
- Diego Envoy proxy: [`diego-release/.../envoy-builder`](https://github.com/cloudfoundry/diego-release/tree/develop/src/code.cloudfoundry.org/envoy-builder)
199+
- 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)
200+
201+
202+
## Key Repositories
203+
204+
| Repository | Changes |
205+
|------------|---------|
206+
| [routing-release](https://github.com/cloudfoundry/routing-release) | Phase 1a: `mtls_domains` config; Phase 1b: `allowed_sources` enforcement |
207+
| [capi-release](https://github.com/cloudfoundry/capi-release) | Phase 1b: `allowed_sources` route option validation |
208+
| [diego-release](https://github.com/cloudfoundry/diego-release) | Phase 2: Egress proxy configuration |
209+
| [cf-deployment](https://github.com/cloudfoundry/cf-deployment) | Ops-file for enabling mTLS app routing |
210+
211+
212+
## References
213+
214+
- [RFC-0027: Generic Per-Route Features](rfc-0027-generic-per-route-features.md)
215+
- [Container-to-Container Networking](https://docs.cloudfoundry.org/concepts/understand-cf-networking.html)
216+
- [Diego Instance Identity Documentation](https://github.com/cloudfoundry/diego-release/blob/develop/docs/050-app-instance-identity.md)
217+
- [GoRouter Client Certificate Configuration](https://github.com/cloudfoundry/routing-release/blob/develop/jobs/gorouter/spec)

0 commit comments

Comments
 (0)