feat(secrets)!: retire the gRPC secrets hub in ScadaBridge — sites get secrets via Deploy Artifacts; rig central on the shared SQL store
This commit is contained in:
+71
-65
@@ -17,59 +17,53 @@
|
||||
x-secrets-store-env: &secrets-store-env
|
||||
Secrets__SqlitePath: "/data/scadabridge-secrets.db"
|
||||
|
||||
# ── Clustered secret replication: the pull-only gRPC hub (scadaproj#3) ─────────
|
||||
# ── Secrets:Store:Provider=SqlServer: central shares one secret store directly (Task 5) ──
|
||||
#
|
||||
# Central hosts the hub on its EXISTING h2c control-plane listener (CentralGrpcPort
|
||||
# 8083, alongside CentralControlService); each site node sweeps it on an interval and
|
||||
# writes what it pulls into its OWN local SQLite store. Nothing but ciphertext crosses
|
||||
# the wire, so every participating node must resolve the SAME KEK.
|
||||
# Task 11 retired secrets replication entirely — both the pull-only gRPC hub (central
|
||||
# hosted, sites swept it) and the SQL-Server ReplicatingSecretStore mode that preceded it.
|
||||
# Sites now receive secrets exclusively via Deploy Artifacts; there is no hub and nothing
|
||||
# for a site to sweep. What remains, and is unrelated to replication, is Secrets:Store:
|
||||
# Provider=SqlServer: it points BOTH central nodes at ONE shared SQL-Server database
|
||||
# directly — no hub, no follower sweep, just two nodes reading and writing the same rows —
|
||||
# so a clustered central pair can never serve sites a drifted secret set. central-a/b set
|
||||
# it explicitly below; site nodes always ignore the key and keep their local SQLite store.
|
||||
#
|
||||
# ENABLED ON FOUR NODES ONLY: the central pair (hub) and the site-a pair (followers).
|
||||
# site-b and site-c are deliberately left without it, so the default-OFF posture is
|
||||
# proven side by side on one rig — exactly as site-a is the rig's only LocalDb-replicated
|
||||
# pair. A node with no Secrets__* override keeps the shipped appsettings default
|
||||
# (Replication:Enabled=false, Mode=SqlServer) and composes a plain local store.
|
||||
#
|
||||
# ALL VALUES HERE ARE DEV-ONLY and committed under the same exception as the mesh PSKs
|
||||
# and the ApiKeyPepper above: a local docker rig needs a working credential in source
|
||||
# control to boot. Production supplies the KEK out of band (ZB_SECRETS_MASTER_KEY, never
|
||||
# committed) and the hub token from appsettings/env — NEVER as a ${secret:} reference,
|
||||
# since resolving one is what the hub exists to make possible.
|
||||
x-secrets-hub-env: &secrets-hub-env
|
||||
# DEV-ONLY KEK — NOT a real key. Identical on all four participating nodes: only
|
||||
# ciphertext replicates, so a node with a different KEK fails closed on resolve with a
|
||||
# kek_id mismatch that reads like corruption but is a deployment error.
|
||||
ZB_SECRETS_MASTER_KEY: "zZiBWuoaVMbJmGXToLk9Lakw0iJozXoL/7Gxac3GwJ4="
|
||||
Secrets__Replication__Enabled: "true"
|
||||
Secrets__Replication__Mode: "Grpc"
|
||||
# DEV-ONLY shared bearer token — NOT a real secret. Presented by every follower and
|
||||
# verified by the hub's fail-closed SecretsHubAuthInterceptor. Must be IDENTICAL on the
|
||||
# hub and every follower; an unset token is a startup failure on both halves.
|
||||
Secrets__GrpcHub__BearerToken: "secrets-hub-docker-dev-token"
|
||||
|
||||
# Central-only half: the SHARED SQL-Server secret store (scadaproj#4, Secrets 0.5.0).
|
||||
# In Grpc mode BOTH central nodes read and write ONE copy of every row in this database,
|
||||
# so the two hub instances serve identical manifests by construction — that is what makes
|
||||
# the site-side FallbackEndpoints below safe. ZbSecretsHub is a dedicated database on the
|
||||
# rig's existing scadabridge-mssql container (default zbsecrets schema, created by the
|
||||
# boot-time SqlServerSecretsStoreMigrator; the database itself + the scadabridge_app grant
|
||||
# were provisioned once via sqlcmd — see docs/plans/2026-08-07-secrets-central-shared-store-live-gate.md).
|
||||
# Same DEV-ONLY credentials as the ScadaBridge__Database__* strings above. Must be a
|
||||
# LITERAL value, never a ${secret:} reference — the pre-host expander needs this string to
|
||||
# reach the store that would resolve it (bootstrap circularity; registration rejects it).
|
||||
# Site nodes must NEVER carry this key: sites talk to central, not to central's database.
|
||||
x-secrets-hub-central-env: &secrets-hub-central-env
|
||||
# ZbSecretsHub is a dedicated database on the rig's existing scadabridge-mssql container
|
||||
# (default zbsecrets schema, created by the boot-time SqlServerSecretsStoreMigrator; the
|
||||
# database itself + the scadabridge_app grant were provisioned once via sqlcmd — see
|
||||
# docs/plans/2026-08-07-secrets-central-shared-store-live-gate.md). Same DEV-ONLY
|
||||
# credentials as the ScadaBridge__Database__* strings below. Must be a LITERAL value,
|
||||
# never a ${secret:} reference — the pre-host expander needs this string to reach the
|
||||
# store that would resolve it (bootstrap circularity; registration rejects it). Site nodes
|
||||
# must NEVER carry this key: sites talk to central, not to central's database.
|
||||
x-secrets-central-store-env: &secrets-central-store-env
|
||||
Secrets__Store__Provider: "SqlServer"
|
||||
Secrets__SqlServer__ConnectionString: "Server=scadabridge-mssql,1433;Database=ZbSecretsHub;User Id=scadabridge_app;Password=ScadaBridge_Dev1#;TrustServerCertificate=true"
|
||||
|
||||
# Site half of the same section. The hub client dials Endpoint first and fails over, per
|
||||
# call, to FallbackEndpoints in order (sticky on whichever answered last, Secrets 0.5.0).
|
||||
# Listing central-b is safe ONLY because both centrals serve the one shared SQL store
|
||||
# above — never list endpoints backed by independent stores: failing over to an emptier
|
||||
# hub is a silent convergence stop, the exact defect (scadaproj#4) the shared store
|
||||
# exists to prevent.
|
||||
x-secrets-hub-site-env: &secrets-hub-site-env
|
||||
Secrets__GrpcHub__Endpoint: "http://scadabridge-central-a:8083"
|
||||
Secrets__GrpcHub__FallbackEndpoints__0: "http://scadabridge-central-b:8083"
|
||||
# ── Deploy-artifact secret KEKs ──────────────────────────────────────────────────────
|
||||
#
|
||||
# Every node that HANDLES a deployed secret — central (it encrypts what ships in the
|
||||
# artifact) and site-a (it decrypts what it receives) — must resolve the SAME
|
||||
# ZB_SECRETS_MASTER_KEY: only ciphertext ever crosses the wire, so a node with a different
|
||||
# KEK fails closed on resolve with a kek_id mismatch that reads like corruption but is a
|
||||
# deployment error. site-b/site-c deliberately carry a DIFFERENT dev KEK (below) so that
|
||||
# mismatch is a real, provable negative on this rig rather than an assumption — see
|
||||
# Task 13.
|
||||
#
|
||||
# ALL VALUES HERE ARE DEV-ONLY and committed under the same exception as the mesh PSKs
|
||||
# and the ApiKeyPepper below: a local docker rig needs a working credential in source
|
||||
# control to boot. Production supplies the KEK out of band (ZB_SECRETS_MASTER_KEY, never
|
||||
# committed).
|
||||
x-secrets-shared-kek-env: &secrets-shared-kek-env
|
||||
# DEV-ONLY KEK — NOT a real key. Identical on central-a/b and site-a-a/b.
|
||||
ZB_SECRETS_MASTER_KEY: "zZiBWuoaVMbJmGXToLk9Lakw0iJozXoL/7Gxac3GwJ4="
|
||||
|
||||
# A DIFFERENT DEV-ONLY KEK — NOT a real key — deliberately mismatched from the one above.
|
||||
# site-b and site-c never receive a deployed secret from this rig's exercised paths today,
|
||||
# but carrying a mismatched key here (rather than none) means a future deploy targeting
|
||||
# them fails closed with a named kek_id mismatch instead of an untested code path.
|
||||
x-secrets-mismatched-kek-env: &secrets-mismatched-kek-env
|
||||
ZB_SECRETS_MASTER_KEY: "esKrmqV2RBmMq/+6T/5zK2BcalRy5gzDXhoG9Qydg6Q="
|
||||
|
||||
services:
|
||||
central-a:
|
||||
@@ -87,9 +81,9 @@ services:
|
||||
stop_grace_period: 30s
|
||||
container_name: scadabridge-central-a
|
||||
environment:
|
||||
# Hub half of the pull-only gRPC secrets hub + the central-only shared SQL store
|
||||
# (anchors at the top of this file).
|
||||
<<: [*secrets-store-env, *secrets-hub-env, *secrets-hub-central-env]
|
||||
# Central-only shared SQL secret store (Secrets:Store:Provider=SqlServer, Task 5) + the
|
||||
# deploy-artifact secret KEK (anchors at the top of this file).
|
||||
<<: [*secrets-store-env, *secrets-shared-kek-env, *secrets-central-store-env]
|
||||
SCADABRIDGE_CONFIG: Central
|
||||
ASPNETCORE_ENVIRONMENT: Development
|
||||
ASPNETCORE_URLS: "http://+:5000"
|
||||
@@ -154,9 +148,9 @@ services:
|
||||
stop_grace_period: 30s
|
||||
container_name: scadabridge-central-b
|
||||
environment:
|
||||
# Hub half of the pull-only gRPC secrets hub + the central-only shared SQL store
|
||||
# (anchors at the top of this file).
|
||||
<<: [*secrets-store-env, *secrets-hub-env, *secrets-hub-central-env]
|
||||
# Central-only shared SQL secret store (Secrets:Store:Provider=SqlServer, Task 5) + the
|
||||
# deploy-artifact secret KEK (anchors at the top of this file).
|
||||
<<: [*secrets-store-env, *secrets-shared-kek-env, *secrets-central-store-env]
|
||||
SCADABRIDGE_CONFIG: Central
|
||||
ASPNETCORE_ENVIRONMENT: Development
|
||||
ASPNETCORE_URLS: "http://+:5000"
|
||||
@@ -221,9 +215,11 @@ services:
|
||||
stop_grace_period: 30s
|
||||
container_name: scadabridge-site-a-a
|
||||
environment:
|
||||
# Follower half of the pull-only gRPC secrets hub (anchors at the top of this
|
||||
# file). site-b and site-c deliberately carry neither.
|
||||
<<: [*secrets-store-env, *secrets-hub-env, *secrets-hub-site-env]
|
||||
# The shared deploy-artifact secret KEK (anchors at the top of this file) — this node
|
||||
# must decrypt what central ships in a deploy artifact. site-b and site-c deliberately
|
||||
# carry a DIFFERENT (mismatched) KEK instead, so that failure mode is provable on this
|
||||
# rig rather than assumed (Task 13).
|
||||
<<: [*secrets-store-env, *secrets-shared-kek-env]
|
||||
SCADABRIDGE_CONFIG: Site
|
||||
# Register row 63. The central containers have always set this; the site containers
|
||||
# never did, so the Development-only DI scope validation (register row 50,
|
||||
@@ -258,9 +254,11 @@ services:
|
||||
stop_grace_period: 30s
|
||||
container_name: scadabridge-site-a-b
|
||||
environment:
|
||||
# Follower half of the pull-only gRPC secrets hub (anchors at the top of this
|
||||
# file). site-b and site-c deliberately carry neither.
|
||||
<<: [*secrets-store-env, *secrets-hub-env, *secrets-hub-site-env]
|
||||
# The shared deploy-artifact secret KEK (anchors at the top of this file) — this node
|
||||
# must decrypt what central ships in a deploy artifact. site-b and site-c deliberately
|
||||
# carry a DIFFERENT (mismatched) KEK instead, so that failure mode is provable on this
|
||||
# rig rather than assumed (Task 13).
|
||||
<<: [*secrets-store-env, *secrets-shared-kek-env]
|
||||
SCADABRIDGE_CONFIG: Site
|
||||
# Register row 63. The central containers have always set this; the site containers
|
||||
# never did, so the Development-only DI scope validation (register row 50,
|
||||
@@ -295,7 +293,9 @@ services:
|
||||
stop_grace_period: 30s
|
||||
container_name: scadabridge-site-b-a
|
||||
environment:
|
||||
<<: *secrets-store-env
|
||||
# A DIFFERENT (mismatched) deploy-artifact secret KEK from central/site-a's — see the
|
||||
# anchor comment at the top of this file (Task 13's mismatch negative).
|
||||
<<: [*secrets-store-env, *secrets-mismatched-kek-env]
|
||||
SCADABRIDGE_CONFIG: Site
|
||||
# Register row 63. The central containers have always set this; the site containers
|
||||
# never did, so the Development-only DI scope validation (register row 50,
|
||||
@@ -329,7 +329,9 @@ services:
|
||||
stop_grace_period: 30s
|
||||
container_name: scadabridge-site-b-b
|
||||
environment:
|
||||
<<: *secrets-store-env
|
||||
# A DIFFERENT (mismatched) deploy-artifact secret KEK from central/site-a's — see the
|
||||
# anchor comment at the top of this file (Task 13's mismatch negative).
|
||||
<<: [*secrets-store-env, *secrets-mismatched-kek-env]
|
||||
SCADABRIDGE_CONFIG: Site
|
||||
# Register row 63. The central containers have always set this; the site containers
|
||||
# never did, so the Development-only DI scope validation (register row 50,
|
||||
@@ -363,7 +365,9 @@ services:
|
||||
stop_grace_period: 30s
|
||||
container_name: scadabridge-site-c-a
|
||||
environment:
|
||||
<<: *secrets-store-env
|
||||
# A DIFFERENT (mismatched) deploy-artifact secret KEK from central/site-a's — see the
|
||||
# anchor comment at the top of this file (Task 13's mismatch negative).
|
||||
<<: [*secrets-store-env, *secrets-mismatched-kek-env]
|
||||
SCADABRIDGE_CONFIG: Site
|
||||
# Register row 63. The central containers have always set this; the site containers
|
||||
# never did, so the Development-only DI scope validation (register row 50,
|
||||
@@ -397,7 +401,9 @@ services:
|
||||
stop_grace_period: 30s
|
||||
container_name: scadabridge-site-c-b
|
||||
environment:
|
||||
<<: *secrets-store-env
|
||||
# A DIFFERENT (mismatched) deploy-artifact secret KEK from central/site-a's — see the
|
||||
# anchor comment at the top of this file (Task 13's mismatch negative).
|
||||
<<: [*secrets-store-env, *secrets-mismatched-kek-env]
|
||||
SCADABRIDGE_CONFIG: Site
|
||||
# Register row 63. The central containers have always set this; the site containers
|
||||
# never did, so the Development-only DI scope validation (register row 50,
|
||||
|
||||
Reference in New Issue
Block a user