Skip to content

Instantly share code, notes, and snippets.

@rkoster
Created April 22, 2026 14:07
Show Gist options
  • Select an option

  • Save rkoster/1f25ffeabdf951dbf7c34856d0a22d51 to your computer and use it in GitHub Desktop.

Select an option

Save rkoster/1f25ffeabdf951dbf7c34856d0a22d51 to your computer and use it in GitHub Desktop.
Cloud Foundry XFCC Header Tester - Test X-Forwarded-Client-Cert format in CF mTLS environments

Cloud Foundry XFCC Header Tester

A simple Go application to test X-Forwarded-Client-Cert (XFCC) header format in Cloud Foundry environments with mTLS app-to-app routing.

Purpose

This tool verifies that GoRouter correctly implements the Envoy XFCC format when routing requests over mTLS domains. It checks whether the XFCC header contains:

  • Envoy format: Hash=<hash>;Subject="<subject>";URI=<uri>
  • Raw PEM certificate (bug): Cert="-----BEGIN CERTIFICATE-----\n..."

Prerequisites

  1. Cloud Foundry deployment with mTLS app-to-app routing enabled
  2. mTLS domain configured (e.g., *.apps.identity)
  3. CF CLI with route policy plugin (for authorization)
  4. Go buildpack available in your CF deployment

Files

  • main.go - Go HTTP server with XFCC testing endpoints
  • go.mod - Go module definition
  • manifest.yml - CF deployment manifest
  • README.md - This file

Deployment

1. Clone or Download Files

Create a directory and save all files:

mkdir xfcc-tester
cd xfcc-tester
# Save main.go, go.mod, and manifest.yml here

2. Update Manifest (if needed)

Edit manifest.yml if your mTLS domain is different from apps.identity:

routes:
- route: xfcc-tester.YOUR-MTLS-DOMAIN

3. Push the App

cf push

The app will:

  • Build using the Go buildpack
  • Deploy to the mTLS domain
  • Start listening on the assigned PORT

4. Add Route Policy

Allow the app to call itself over mTLS:

# Get the app GUID
APP_GUID=$(cf app xfcc-tester --guid)

# Add route policy (allows app to call itself)
cf add-route-policy xfcc-tester.apps.identity \
  --hostname xfcc-tester \
  --source-app xfcc-tester

Note: Route policies take ~30 seconds to propagate to all GoRouters.

5. Test the XFCC Header

Wait 30 seconds after adding the route policy, then test:

# From inside the container
cf ssh xfcc-tester -c "curl -s http://localhost:8080/test-xfcc" | jq .

# Expected output:
# {
#   "status": "success",
#   "my_route": "xfcc-tester.apps.identity",
#   "echo_response": {
#     "has_xfcc": true,
#     "xfcc_header": "Hash=...;Subject=\"CN=...,OU=app:...\""
#   }
# }

Endpoints

GET /

Home page with links to all endpoints.

GET /echo

Returns the XFCC header received in the request.

Response:

{
  "xfcc_header": "Hash=...;Subject=\"...\"",
  "has_xfcc": true
}

GET /test-xfcc

Makes an mTLS request to itself via GoRouter and returns the XFCC header that GoRouter added.

Response:

{
  "status": "success",
  "my_route": "xfcc-tester.apps.identity",
  "echo_response": {
    "xfcc_header": "Hash=...;Subject=\"...\"",
    "has_xfcc": true
  }
}

GET /env

Shows environment variables for debugging.

How It Works

  1. The app exposes /echo endpoint that captures and returns the X-Forwarded-Client-Cert header
  2. The /test-xfcc endpoint:
    • Uses Diego instance identity certificate (CF_INSTANCE_CERT, CF_INSTANCE_KEY)
    • Makes HTTPS request to https://xfcc-tester.apps.identity/echo
    • GoRouter intercepts the request, validates the mTLS cert, and adds/sets the XFCC header
    • The /echo endpoint receives the request and returns the XFCC header value
  3. The response shows what XFCC format GoRouter is using

Interpreting Results

✅ Correct (Envoy Format)

{
  "xfcc_header": "Hash=abc123...;Subject=\"CN=...,OU=app:...\""
}
  • Header contains structured Hash= and Subject= fields
  • No raw PEM certificate included
  • GoRouter is correctly implementing Envoy XFCC format

❌ Bug (Raw PEM Certificate)

{
  "xfcc_header": "Cert=\"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\";..."
}
  • Header contains Cert= field with full PEM-encoded certificate
  • This is the bug described in the issue report

Troubleshooting

App fails to start

  • Check logs: cf logs xfcc-tester --recent
  • Ensure Go buildpack is available: cf buildpacks
  • Verify the app has enough memory (128M minimum)

"authorization denied" errors

  • Verify route policy was added: cf route-policies
  • Wait 30 seconds for route policy to propagate
  • Check route policy source and destination match the app GUID

"connection refused" or timeout

  • Verify the mTLS domain is configured in GoRouter
  • Check GoRouter logs for routing errors
  • Ensure Diego instance identity is enabled
  • Verify BOSH DNS is resolving *.apps.identity to router instances

XFCC header is empty

  • GoRouter may not be configured for mTLS on this domain
  • Check router.mtls_domains in GoRouter job properties
  • Verify client certificate is being sent (check app logs)

Environment Variables

The app uses these CF-provided environment variables:

  • PORT - HTTP server port (assigned by CF)
  • CF_INSTANCE_CERT - Diego instance identity certificate path
  • CF_INSTANCE_KEY - Diego instance identity private key path
  • VCAP_APPLICATION - CF application metadata (for debugging)

Architecture Notes

This is a self-referencing test app:

  • The app makes mTLS requests to itself via the GoRouter
  • This allows testing XFCC without needing a separate backend app
  • The route policy allows the app to authorize requests to itself

Related Documentation

License

Public Domain / CC0

Author

Created for testing Cloud Foundry mTLS app-to-app routing implementations.

module xfcc-tester
go 1.23
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"crypto/tls"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
certFile := os.Getenv("CF_INSTANCE_CERT")
keyFile := os.Getenv("CF_INSTANCE_KEY")
http.HandleFunc("/", homeHandler)
http.HandleFunc("/echo", echoHandler)
http.HandleFunc("/test-xfcc", func(w http.ResponseWriter, r *http.Request) {
testXFCCHandler(w, r, certFile, keyFile)
})
http.HandleFunc("/env", envHandler)
log.Printf("XFCC Tester starting on port %s", port)
log.Printf("CF_INSTANCE_CERT: %s", certFile)
log.Printf("CF_INSTANCE_KEY: %s", keyFile)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
fmt.Fprint(w, `<html><body><h1>XFCC Tester</h1><ul>
<li><a href="/test-xfcc">/test-xfcc</a> - Call self with mTLS to test XFCC header</li>
<li><a href="/echo">/echo</a> - Echo received XFCC header</li>
<li><a href="/env">/env</a> - Show environment</li>
</ul></body></html>`)
}
func echoHandler(w http.ResponseWriter, r *http.Request) {
xfcc := r.Header.Get("X-Forwarded-Client-Cert")
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"xfcc_header": xfcc,
"has_xfcc": xfcc != "",
})
}
func testXFCCHandler(w http.ResponseWriter, r *http.Request, certFile, keyFile string) {
myRoute := "xfcc-tester.apps.identity"
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "error",
"error": fmt.Sprintf("Failed to load cert: %v", err),
})
return
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
},
},
}
resp, err := client.Get(fmt.Sprintf("https://%s/echo", myRoute))
if err != nil {
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "error",
"error": fmt.Sprintf("Request failed: %v", err),
})
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var echoResp map[string]interface{}
json.Unmarshal(body, &echoResp)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "success",
"my_route": myRoute,
"echo_response": echoResp,
})
}
func envHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"cf_instance_cert": os.Getenv("CF_INSTANCE_CERT"),
"cf_instance_key": os.Getenv("CF_INSTANCE_KEY"),
"port": os.Getenv("PORT"),
"vcap_application": os.Getenv("VCAP_APPLICATION"),
})
}
---
applications:
- name: xfcc-tester
memory: 128M
disk_quota: 256M
instances: 1
buildpacks:
- go_buildpack
routes:
- route: xfcc-tester.apps.identity
health-check-type: process
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment