Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Mutual TLS (mTLS) & Zero-Trust Networking

The gateway implements mutual TLS for subgraph communication, providing cryptographic authentication for every service-to-service call. This follows the Zero-Trust principle: never trust, always verify.

Architecture

┌──────────────┐          mTLS          ┌──────────────┐
│              │◄──────────────────────►│              │
│   Gateway    │  SPIFFE SVID Identity  │  Subgraph    │
│   (Router)   │  X.509 Certificates    │  (Users)     │
│              │  ECDSA P-256           │              │
├──────────────┤                        └──────────────┘
│ Ephemeral CA │          mTLS          ┌──────────────┐
│ SVID Rotation│◄──────────────────────►│              │
│ Trust Bundle │  Both sides verify     │  Subgraph    │
│              │  each other's cert     │  (Products)  │
└──────────────┘                        └──────────────┘

Both the router and subgraphs present certificates and verify each other’s identity. There are no plaintext connections.

Security Properties

PropertyDescription
Mutual AuthenticationBoth client and server present and verify X.509 certificates
SPIFFE IdentityWorkload identities follow the SPIFFE standard: spiffe://{domain}/ns/{namespace}/sa/{service}
Short-Lived CertificatesDefault TTL of 1 hour; automatically rotated at 50% lifetime
Ephemeral CAIn-process Certificate Authority using ECDSA P-256 — no external PKI needed for development
ECDSA P-256Modern elliptic curve cryptography for key generation
TLS 1.2+Minimum TLS version enforced on all connections

Quick Start

1. Enable mTLS in router.yaml

# Global mTLS configuration
mtls:
  enabled: true
  trust_domain: "cluster.local"
  service_name: "router"
  namespace: "default"
  cert_ttl_secs: 3600              # 1 hour
  trust_bundle_path: "/etc/certs/trust-bundle.pem"  # Optional
  verify_hostname: true
  allow_fallback: false            # Strict mode — no plain HTTP

# Per-subgraph mTLS overrides
subgraphs:
  - name: "users"
    url: "https://users-service:4001/graphql"
    mtls:
      enabled: true
      trust_domain: "cluster.local"
      service_name: "users-service"

2. Ephemeral CA (Development / Testing)

For local development, the gateway automatically creates an ephemeral CA at startup:

use grpc_graphql_gateway::mtls::{MtlsConfig, MtlsProvider};

let config = MtlsConfig {
    enabled: true,
    trust_domain: "dev.local".to_string(),
    service_name: "gateway".to_string(),
    namespace: "default".to_string(),
    cert_ttl_secs: 3600,
    ..Default::default()
};

let provider = MtlsProvider::new(config)?;
// Provider automatically generates CA + initial SVID

3. External CA (Production)

For production, provide your own CA certificate and key:

mtls:
  enabled: true
  trust_domain: "production.example.com"
  ca_cert_path: "/etc/certs/ca.pem"
  ca_key_path: "/etc/certs/ca-key.pem"
  trust_bundle_path: "/etc/certs/trust-bundle.pem"
use grpc_graphql_gateway::mtls::CertificateAuthority;

let ca = CertificateAuthority::from_files(
    "/etc/certs/ca.pem",
    "/etc/certs/ca-key.pem",
    "production.example.com",
)?;

SPIFFE Identity Model

Every workload gets a SPIFFE Verifiable Identity Document (SVID) with a URI like:

spiffe://cluster.local/ns/default/sa/router
spiffe://cluster.local/ns/default/sa/users-service
spiffe://cluster.local/ns/payments/sa/billing-service

Components

ComponentExampleDescription
Trust Domaincluster.localOrganizational boundary
NamespacedefaultKubernetes namespace or logical group
Service AccountrouterUnique service identifier

Certificate Lifecycle

┌──────────┐     Issue      ┌──────────┐
│          │───────────────►│          │
│    CA    │                │   SVID   │  TTL: 1 hour
│          │◄───────────────│          │  Auto-rotate at 30 min
│          │    Rotate      │          │
└──────────┘                └──────────┘
                                │
                                ▼
                      ┌──────────────────┐
                      │  reqwest::Client  │
                      │  with mTLS ID    │
                      └──────────────────┘

Automatic Rotation

The MtlsProvider starts a background task that:

  1. Checks every 30 seconds if the current SVID needs renewal
  2. Renews at 50% lifetime (i.e., 30 minutes for a 1-hour TTL)
  3. Emergency rotation if the certificate has already expired
  4. Atomic swap — in-flight requests continue with the old cert; new requests get the fresh one
use std::sync::Arc;

let provider = Arc::new(MtlsProvider::new(config)?);
let rotation_handle = provider.start_rotation_task();

// Provider will automatically rotate SVIDs in the background

Health Check Integration

The mTLS status is available for health probes:

let status = provider.status().await;

println!("Trust Domain: {}", status.trust_domain);
println!("SPIFFE ID:    {}", status.spiffe_id);
println!("Cert Valid:   {}", status.cert_valid);
println!("Remaining:    {}s", status.remaining_secs);
println!("Needs Renewal:{}", status.needs_renewal);

The MtlsStatus struct:

pub struct MtlsStatus {
    pub enabled: bool,
    pub trust_domain: String,
    pub spiffe_id: String,
    pub cert_serial: String,
    pub cert_valid: bool,
    pub remaining_secs: u64,
    pub needs_renewal: bool,
}

Building mTLS Clients

The provider builds reqwest::Client instances pre-configured with:

  • The current SVID as the TLS client identity
  • The CA trust bundle for verifying subgraph certificates
  • TLS 1.2 minimum version
  • Optional hostname verification
// Build a client with the current SVID
let client = provider.build_client().await?;

// Use it for subgraph requests
let response = client
    .post("https://users-service:4001/graphql")
    .json(&graphql_request)
    .send()
    .await?;

Issuing Subgraph SVIDs

For testing or development, the gateway can issue SVIDs for subgraphs:

use grpc_graphql_gateway::mtls::{issue_subgraph_svid, export_svid};
use std::time::Duration;

let ca = provider.ca();

// Issue an SVID for the users subgraph
let svid = issue_subgraph_svid(
    ca,
    "users-service",
    "default",
    Duration::from_secs(3600),
)?;

// Export to files for the subgraph to use
export_svid(&svid, "/etc/certs/users-cert.pem", "/etc/certs/users-key.pem")?;

// Export trust bundle for subgraphs to verify the gateway
ca.export_trust_bundle("/etc/certs/trust-bundle.pem")?;

Configuration Reference

MtlsConfig

FieldTypeDefaultDescription
enabledboolfalseEnable/disable mTLS
trust_domainString"cluster.local"SPIFFE trust domain
service_nameString"router"Service identity name
namespaceString"default"Kubernetes namespace
cert_ttl_secsu643600Certificate TTL in seconds
ca_cert_pathOption<String>NoneExternal CA cert (PEM). Ephemeral CA if not set
ca_key_pathOption<String>NoneExternal CA key (PEM). Ephemeral CA if not set
trust_bundle_pathOption<String>NonePath to export trust bundle
allow_fallbackboolfalseAllow plain HTTP if mTLS fails
verify_hostnamebooltrueVerify subgraph TLS hostname

Production Recommendations

1. Use an External CA

In production, provide your own CA rather than relying on the ephemeral CA:

mtls:
  ca_cert_path: "/etc/certs/ca.pem"
  ca_key_path: "/etc/certs/ca-key.pem"

2. Short TTLs

Keep certificate TTLs short (1 hour or less). The automatic rotation handles renewals seamlessly:

mtls:
  cert_ttl_secs: 3600  # 1 hour

3. Never Allow Fallback in Production

mtls:
  allow_fallback: false  # Strict mode

4. Export Trust Bundles for Subgraphs

Subgraphs need the CA trust bundle to verify the gateway’s certificate:

mtls:
  trust_bundle_path: "/shared/certs/trust-bundle.pem"

5. Monitor Certificate Health

Integrate MtlsStatus into your health check endpoint to detect certificate issues before they cause outages.

6. Kubernetes Integration

In Kubernetes, mount cert paths from Secrets or use a sidecar like SPIRE for production-grade SPIFFE identity management:

# Kubernetes deployment snippet
volumes:
  - name: certs
    secret:
      secretName: gateway-mtls-certs
containers:
  - name: gateway
    volumeMounts:
      - name: certs
        mountPath: /etc/certs
        readOnly: true

Prerequisites

  • OpenSSL must be installed on the system (used for certificate generation via CLI)
  • For production, pre-generated CA certificates are recommended over the ephemeral CA