Merge pull request #1143 from pikasTech/issue-2234-secret-plane-control
feat: expose secret-plane cluster store
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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`],
|
||||
]),
|
||||
|
||||
Reference in New Issue
Block a user