Merge pull request #1143 from pikasTech/issue-2234-secret-plane-control

feat: expose secret-plane cluster store
This commit is contained in:
Lyon
2026-06-27 17:52:06 +08:00
committed by GitHub
2 changed files with 53 additions and 10 deletions
+1
View File
@@ -43,6 +43,7 @@ vault:
tokenLengthBytes: 32
syncProbe:
secretStoreName: hwlab-secret-plane-vault
clusterSecretStoreName: hwlab-secret-plane-vault-cluster
externalSecretName: hwlab-secret-plane-poc
targetSecretName: hwlab-secret-plane-poc-sync
refreshInterval: 15s
+52 -10
View File
@@ -62,6 +62,7 @@ interface VaultConfig {
interface SyncProbeConfig {
secretStoreName: string;
clusterSecretStoreName: string;
externalSecretName: string;
targetSecretName: string;
refreshInterval: string;
@@ -227,6 +228,7 @@ function parseSyncProbe(record: Record<string, unknown>): SyncProbeConfig {
if (!/^[A-Za-z0-9._-]+$/u.test(remoteProperty)) throw new Error(`${configLabel}.syncProbe.remoteProperty must be a simple key`);
return {
secretStoreName: y.kubernetesNameField(record, "secretStoreName", "syncProbe"),
clusterSecretStoreName: y.kubernetesNameField(record, "clusterSecretStoreName", "syncProbe"),
externalSecretName: y.kubernetesNameField(record, "externalSecretName", "syncProbe"),
targetSecretName: y.kubernetesNameField(record, "targetSecretName", "syncProbe"),
refreshInterval: y.stringField(record, "refreshInterval", "syncProbe"),
@@ -526,6 +528,27 @@ spec:
key: ${vault.tokenSecretKey}
---
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: ${probe.clusterSecretStoreName}
labels:
app.kubernetes.io/name: ${probe.clusterSecretStoreName}
app.kubernetes.io/component: cluster-secretstore
app.kubernetes.io/part-of: platform-infra
app.kubernetes.io/managed-by: unidesk
spec:
provider:
vault:
server: "http://${vault.serviceName}.${target.namespace}.svc.cluster.local:${vault.port}"
path: ${probe.vaultMountPath}
version: v2
auth:
tokenSecretRef:
name: ${vault.tokenSecretName}
key: ${vault.tokenSecretKey}
namespace: ${target.namespace}
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: ${probe.externalSecretName}
@@ -615,7 +638,7 @@ for doc in re.split(r"^---\\s*$", text, flags=re.M):
continue
match = re.search(r"^\\s*kind:\\s*([A-Za-z0-9]+)\\s*$", doc, flags=re.M)
kind = match.group(1) if match else ""
if kind in {"SecretStore", "ExternalSecret"}:
if kind in {"SecretStore", "ClusterSecretStore", "ExternalSecret"}:
custom_docs.append(doc.strip() + "\\n")
else:
core_docs.append(doc.strip() + "\\n")
@@ -637,7 +660,7 @@ if kubectl get crd ${secretPlane.eso.crds.map((name) => name).join(" ")} >/dev/n
else
custom_rc=0
custom_disposition=skipped-crds-missing
printf '%s\\n' 'SecretStore/ExternalSecret dry-run skipped because ESO CRDs are not installed yet; real apply installs CRDs first.' >"$custom_out"
printf '%s\\n' 'SecretStore/ClusterSecretStore/ExternalSecret dry-run skipped because ESO CRDs are not installed yet; real apply installs CRDs first.' >"$custom_out"
: >"$custom_err"
fi
if kubectl get namespace ${target.namespace} >/dev/null 2>&1; then
@@ -804,6 +827,7 @@ capture_json pods kubectl -n ${target.namespace} get pods -l app.kubernetes.io/p
capture_json services kubectl -n ${target.namespace} get service ${secretPlane.vault.serviceName}
capture_json crds kubectl get ${crdArgs}
capture_json secretstore kubectl -n ${target.namespace} get secretstore ${secretPlane.syncProbe.secretStoreName}
capture_json clustersecretstore kubectl get clustersecretstore ${secretPlane.syncProbe.clusterSecretStoreName}
capture_json externalsecret kubectl -n ${target.namespace} get externalsecret ${secretPlane.syncProbe.externalSecretName}
capture_json targetsecret kubectl -n ${target.namespace} get secret ${secretPlane.syncProbe.targetSecretName}
capture_json tokensecret kubectl -n ${target.namespace} get secret ${secretPlane.vault.tokenSecretName}
@@ -857,6 +881,8 @@ def condition_summary(item):
{"type": c.get("type"), "status": c.get("status"), "reason": c.get("reason"), "message": c.get("message")}
for c in status.get("conditions", [])
]
def ready_condition(item):
return any(c.get("type") == "Ready" and c.get("status") == "True" for c in condition_summary(item))
def secret_key_summary(item, keys):
data = (item or {}).get("data") or {}
out = {"name": (item or {}).get("metadata", {}).get("name"), "ready": bool(data), "keys": sorted(data.keys()), "missingKeys": [key for key in keys if key not in data], "fingerprints": {}, "valuesPrinted": False}
@@ -876,14 +902,17 @@ crd_names = sorted([item.get("metadata", {}).get("name") for item in crds if isi
target_secret = secret_key_summary(load("targetsecret"), ["${secretPlane.syncProbe.remoteProperty}"])
token_secret = secret_key_summary(load("tokensecret"), ["${secretPlane.vault.tokenSecretKey}"])
secretstore = load("secretstore") or {}
clustersecretstore = load("clustersecretstore") or {}
externalsecret = load("externalsecret") or {}
eso_ready = all(deployment_ready.get(name) is True for name in ${JSON.stringify(esoDeployments)})
vault_ready = deployment_ready.get("${secretPlane.vault.deploymentName}") is True
consumer_ready = deployment_ready.get("${secretPlane.syncProbe.consumer.deploymentName}") is True
crds_ready = all(name in crd_names for name in ${JSON.stringify(secretPlane.eso.crds)})
secretstore_ready = ready_condition(secretstore)
clustersecretstore_ready = ready_condition(clustersecretstore)
synced = target_secret["fingerprints"].get("${secretPlane.syncProbe.remoteProperty}") == "${secretPlane.syncProbe.expectedFingerprint}"
payload = {
"ready": eso_ready and vault_ready and crds_ready,
"ready": eso_ready and vault_ready and crds_ready and secretstore_ready and clustersecretstore_ready,
"target": "${target.id}",
"route": "${target.route}",
"namespace": "${target.namespace}",
@@ -891,11 +920,14 @@ payload = {
"crdsReady": crds_ready,
"esoReady": eso_ready,
"vaultReady": vault_ready,
"secretStoreReady": secretstore_ready,
"clusterSecretStoreReady": clustersecretstore_ready,
"consumerReady": consumer_ready,
"syncReady": synced,
"deployments": deployments,
"services": [service_summary(item) for item in list_items("services")],
"secretStore": {"name": "${secretPlane.syncProbe.secretStoreName}", "conditions": condition_summary(secretstore)},
"clusterSecretStore": {"name": "${secretPlane.syncProbe.clusterSecretStoreName}", "conditions": condition_summary(clustersecretstore)},
"externalSecret": {"name": "${secretPlane.syncProbe.externalSecretName}", "conditions": condition_summary(externalsecret), "refreshTime": (externalsecret.get("status") or {}).get("refreshTime")},
"targetSecret": target_secret,
"tokenSecret": {"name": token_secret["name"], "ready": token_secret["ready"], "keys": token_secret["keys"], "missingKeys": token_secret["missingKeys"], "valuesPrinted": False},
@@ -978,13 +1010,15 @@ else
fi
kubectl -n ${target.namespace} get secretstore ${secretPlane.syncProbe.secretStoreName} -o json >"$tmp/secretstore.json" 2>"$tmp/secretstore.err"
secretstore_rc=$?
kubectl get clustersecretstore ${secretPlane.syncProbe.clusterSecretStoreName} -o json >"$tmp/clustersecretstore.json" 2>"$tmp/clustersecretstore.err"
clustersecretstore_rc=$?
kubectl -n ${target.namespace} get externalsecret ${secretPlane.syncProbe.externalSecretName} -o json >"$tmp/externalsecret.json" 2>"$tmp/externalsecret.err"
externalsecret_rc=$?
python3 - "$vault_put_rc" "$vault_metadata_rc" "$sync_rc" "$consumer_rollout_restart_rc" "$consumer_rollout_rc" "$consumer_env_rc" "$secretstore_rc" "$externalsecret_rc" "$secret_fingerprint" "$tmp" <<'PY'
python3 - "$vault_put_rc" "$vault_metadata_rc" "$sync_rc" "$consumer_rollout_restart_rc" "$consumer_rollout_rc" "$consumer_env_rc" "$secretstore_rc" "$clustersecretstore_rc" "$externalsecret_rc" "$secret_fingerprint" "$tmp" <<'PY'
import json, os, sys
vault_put_rc, vault_metadata_rc, sync_rc, consumer_rollout_restart_rc, consumer_rollout_rc, consumer_env_rc, secretstore_rc, externalsecret_rc = [int(value) for value in sys.argv[1:9]]
secret_fingerprint = sys.argv[9]
tmp = sys.argv[10]
vault_put_rc, vault_metadata_rc, sync_rc, consumer_rollout_restart_rc, consumer_rollout_rc, consumer_env_rc, secretstore_rc, clustersecretstore_rc, externalsecret_rc = [int(value) for value in sys.argv[1:10]]
secret_fingerprint = sys.argv[10]
tmp = sys.argv[11]
def text(name, limit=3000):
try:
return open(os.path.join(tmp, name), encoding="utf-8", errors="replace").read()[-limit:]
@@ -999,7 +1033,7 @@ def conditions(obj):
status = (obj or {}).get("status") or {}
return [{"type": c.get("type"), "status": c.get("status"), "reason": c.get("reason"), "message": c.get("message")} for c in status.get("conditions", [])]
payload = {
"ok": vault_put_rc == 0 and vault_metadata_rc == 0 and sync_rc == 0 and consumer_rollout_restart_rc == 0 and consumer_rollout_rc == 0 and consumer_env_rc == 0 and secretstore_rc == 0 and externalsecret_rc == 0,
"ok": vault_put_rc == 0 and vault_metadata_rc == 0 and sync_rc == 0 and consumer_rollout_restart_rc == 0 and consumer_rollout_rc == 0 and consumer_env_rc == 0 and secretstore_rc == 0 and clustersecretstore_rc == 0 and externalsecret_rc == 0,
"target": "${target.id}",
"namespace": "${target.namespace}",
"checks": {
@@ -1027,6 +1061,7 @@ payload = {
"stderrTail": text("consumer-env.err"),
},
"secretStore": {"exitCode": secretstore_rc, "conditions": conditions(load("secretstore.json")), "stderrTail": text("secretstore.err")},
"clusterSecretStore": {"exitCode": clustersecretstore_rc, "conditions": conditions(load("clustersecretstore.json")), "stderrTail": text("clustersecretstore.err")},
"externalSecret": {"exitCode": externalsecret_rc, "conditions": conditions(load("externalsecret.json")), "stderrTail": text("externalsecret.err")},
},
"boundary": "platform-infra only; no HWLAB namespace or workload integration was created",
@@ -1089,8 +1124,9 @@ function vaultSummary(secretPlane: SecretPlaneConfig, target: SecretPlaneTarget)
function syncProbeSummary(secretPlane: SecretPlaneConfig): Record<string, unknown> {
const probe = secretPlane.syncProbe;
return {
dataFlow: "Vault KV v2 -> ESO SecretStore -> ExternalSecret -> Kubernetes Secret -> consumer env",
dataFlow: "Vault KV v2 -> ESO SecretStore/ClusterSecretStore -> ExternalSecret -> Kubernetes Secret -> consumer env",
secretStoreName: probe.secretStoreName,
clusterSecretStoreName: probe.clusterSecretStoreName,
externalSecretName: probe.externalSecretName,
targetSecretName: probe.targetSecretName,
refreshInterval: probe.refreshInterval,
@@ -1117,7 +1153,7 @@ function policyChecks(secretPlane: SecretPlaneConfig, target: SecretPlaneTarget,
{ name: "no-hwlab-workloads", ok: !/namespace:\s*hwlab/iu.test(yaml) && !/hwlab-v0?3/iu.test(yaml), detail: "This PoC must not integrate into HWLAB v0.3 yet." },
{ name: "no-nodeport-or-loadbalancer", ok: !/^\s*type:\s*(NodePort|LoadBalancer)\s*$/mu.test(yaml), detail: "Secret plane services stay ClusterIP-only." },
{ name: "no-hardcoded-token", ok: !yaml.includes("VAULT_DEV_ROOT_TOKEN_ID:") && secretPlane.vault.bootstrap.tokenMode === "generated-if-missing", detail: "Vault dev token is generated into a Kubernetes Secret when missing and never committed." },
{ name: "required-objects-rendered", ok: kinds.includes("Deployment") && kinds.includes("SecretStore") && kinds.includes("ExternalSecret"), detail: "Vault backend, ESO SecretStore and ExternalSecret PoC objects are rendered from YAML." },
{ name: "required-objects-rendered", ok: kinds.includes("Deployment") && kinds.includes("SecretStore") && kinds.includes("ClusterSecretStore") && kinds.includes("ExternalSecret"), detail: "Vault backend, ESO SecretStore/ClusterSecretStore and ExternalSecret PoC objects are rendered from YAML." },
];
}
@@ -1139,10 +1175,13 @@ function compactStatus(parsed: Record<string, unknown>, full: boolean): Record<s
crdsReady: parsed.crdsReady,
esoReady: parsed.esoReady,
vaultReady: parsed.vaultReady,
secretStoreReady: parsed.secretStoreReady,
clusterSecretStoreReady: parsed.clusterSecretStoreReady,
consumerReady: parsed.consumerReady,
syncReady: parsed.syncReady,
deployments: parsed.deployments,
secretStore: parsed.secretStore,
clusterSecretStore: parsed.clusterSecretStore,
externalSecret: parsed.externalSecret,
targetSecret: parsed.targetSecret,
tokenSecret: parsed.tokenSecret,
@@ -1165,6 +1204,7 @@ function renderPlan(result: Record<string, unknown>): RenderedCliResult {
["NAMESPACE", stringValue(target.namespace), "role", stringValue(target.role)],
["ESO", stringValue(eso.version), "manifest", stringValue(eso.manifestUrl)],
["VAULT", stringValue(vault.deploymentName), "service", stringValue(vault.serviceDns)],
["STORE", stringValue(syncProbe.secretStoreName), "clusterStore", stringValue(syncProbe.clusterSecretStoreName)],
["SYNC", stringValue(syncProbe.externalSecretName), "targetSecret", stringValue(syncProbe.targetSecretName)],
["POLICY", failed.length === 0 ? "ok" : `failed=${failed.length}`, "valuesPrinted", "false"],
];
@@ -1205,6 +1245,8 @@ function renderStatus(result: Record<string, unknown>): RenderedCliResult {
["crds", boolText(summary.crdsReady), "ESO API installed"],
["eso", boolText(summary.esoReady), "controller/webhook/cert-controller"],
["vault", boolText(summary.vaultReady), "Vault dev KV v2 backend"],
["secretStore", boolText(summary.secretStoreReady), stringValue(record(summary.secretStore).name)],
["clusterStore", boolText(summary.clusterSecretStoreReady), stringValue(record(summary.clusterSecretStore).name)],
["sync", boolText(summary.syncReady), `secret=${stringValue(targetSecret.name)} key=${stringValue(arrayValues(targetSecret.keys).join(","), "-")}`],
["token", boolText(tokenSecret.ready), `secret=${stringValue(tokenSecret.name)} valuesPrinted=false`],
]),