Keystone Gateway

Configuration examples

Adapt the excerpts below into the gateway.yaml supplied in the release archive, then validate it before use:

/opt/particle_horizon/keystone-gateway config validate --config /opt/particle_horizon/local_config/keystone-gateway/gateway.yaml

Standalone

Use the complete configuration in standalone installation. For production TLS, enable api.https, set its TLS files, use api.auth.mode: mtls, and disable api.http.

Multiple caller principals

The fixed fields under api.auth support only the one shared principal used by mode: none. For multiple installations or both product types, select a mapping-based mode. This mTLS excerpt points the Gateway at a separate principal mapping file:

api:
  auth:
    mode: mtls
  https:
    enabled: true
    listen: "0.0.0.0:8443"
    advertise_url: "https://gateway-a.internal:8443"
    server_cert_file: /opt/particle_horizon/local_config/keystone-gateway/server.crt
    server_key_file: /opt/particle_horizon/local_config/keystone-gateway/server.key
    client_ca_file: /opt/particle_horizon/local_config/keystone-gateway/client-ca.crt
    principal_mappings_file: /opt/particle_horizon/local_config/keystone-gateway/principals.yaml
  http:
    enabled: false
# /opt/particle_horizon/local_config/keystone-gateway/principals.yaml
principals:
  - common_name: product-phocol-01
    principal_id: product-phocol-01
    allowed_license_type: phocol
    operator: false
  - common_name: product-lmc-exporter-01
    principal_id: product-lmc-exporter-01
    allowed_license_type: lmc_exporter
    operator: false

Each entry permits exactly one product license type. Add entries for additional installations; the mapping document has a 1 MiB total size limit rather than a fixed entry-count limit. See client authentication for bearer mappings, validation constraints, and endpoint visibility.

HA A-side

A complete Active/Standby configuration for replica A. Both replicas share gateway_id, the state-encryption key, and a signed license listing both replica IDs; everything else is per-replica. storage.peer.replica_id names the other replica, and the peer channels are private to the pair — publish only the client listener.

gateway_id: gw-acme-production
api:
  http:
    enabled: true
    listen: "10.20.0.10:8080"
    advertise_url: "http://gateway-a.internal:8080"
    allow_insecure_http: true
  auth:
    mode: none
    principal_id: local-operator
    allowed_license_type: phocol
    operator: true
storage:
  directory: /opt/particle_horizon/local_data/keystone-gateway/state
  encryption_key_file: /opt/particle_horizon/local_config/keystone-gateway/state.key
  peer:
    replica_id: gateway-b
    listen: "10.20.0.10:9443"
    target: "10.20.0.11:9443"
    reconcile_listen: "10.20.0.10:9543"
    reconcile_target: "10.20.0.11:9543"
    tls:
      cert_file: /opt/particle_horizon/local_config/keystone-gateway/peer.crt
      key_file: /opt/particle_horizon/local_config/keystone-gateway/peer.key
      ca_file: /opt/particle_horizon/local_config/keystone-gateway/peer-ca.crt
license: { file: /opt/particle_horizon/local_config/keystone-gateway/gateway-license.json }
ha: { enabled: true, replica_id: gateway-a, bootstrap_expect: 2, peer_loss_timeout_seconds: 5 }
leases: { default_ttl_seconds: 120, min_ttl_seconds: 30, max_ttl_seconds: 300, max_clock_skew_seconds: 5, expiry_sweep_seconds: 15 }
reports: { directory: /opt/particle_horizon/local_data/keystone-gateway/reports, retention_days: 90 }
logging: { level: info, format: json }

Peer traffic always uses mutual TLS: each certificate must chain to ca_file, carry a CN matching the peer identity configured on the other node, and carry a SAN matching the address that node targets. This example uses a private HTTP client listener for brevity; for production TLS, enable api.https, set its TLS files, use api.auth.mode: mtls, and disable api.http.

HA B-side delta

Copy the A-side configuration above, then swap all local and target peer values, update the client address, and set unique replica IDs:

api:
  http: { enabled: true, listen: "10.20.0.11:8080", advertise_url: "http://gateway-b.internal:8080", allow_insecure_http: true }
storage:
  peer:
    replica_id: gateway-a
    listen: "10.20.0.11:9443"
    target: "10.20.0.10:9443"
    reconcile_listen: "10.20.0.11:9543"
    reconcile_target: "10.20.0.10:9543"
ha: { enabled: true, replica_id: gateway-b, bootstrap_expect: 2, peer_loss_timeout_seconds: 5 }

Telemetry and air gap

telemetry:
  enabled: true
  protocol: grpc
  otlp_endpoint: "https://otel-collector.internal:4317"
  ca_file: /opt/particle_horizon/local_config/keystone-gateway/otel-ca.crt
  export_timeout_seconds: 10

For air-gapped use, omit telemetry or set enabled: false; no external connectivity is needed for license verification or leasing.

Confirm