CP-25864: fall back cluster_name to cyberark.service_id under Conjur JWT - #827
CP-25864: fall back cluster_name to cyberark.service_id under Conjur JWT#827roeezis wants to merge 2 commits into
Conversation
The MachineHub cluster_name fallback chain (cluster_name -> cluster_id -> ARK_USERNAME -> empty) always resolves empty for wizard-deployed agents: the disco-agent chart never sets cluster_id, and ARK_USERNAME doesn't exist under Conjur JWT auth. Add cyberark.service_id (the Conjur authn-jwt service ID, always set by the onboarding wizard) as a final fallback before empty.
| if clusterName == "" && cfg.CyberArk != nil && cfg.CyberArk.ServiceID != "" { | ||
| // Conjur JWT installs have no ARK_USERNAME to fall back to. | ||
| // cyberark.service_id (the Conjur authn-jwt service ID, | ||
| // typically the cluster UUID) is always set for these |
There was a problem hiding this comment.
typically the cluster UUID does not match what this repository tells users about service_id.
deploy/charts/disco-agent/values.yamldescribesconfig.cyberark.serviceIdas "The Conjur authn-jwt authenticator service ID configured for this tenant".- The README section explaining it is titled "Per-tenant Conjur onboarding".
- The worked
helm upgradeexample in that README passes the literal--set config.cyberark.serviceId=disco-agent. internal/cyberark/client.go:50hedges the other way:// authn-jwt service id (POC: per-cluster, e.g. "dev-cluster").
The genuine per-cluster UUID is already collected elsewhere: Snapshot.ClusterID, sourced from the kube-system namespace UID in pkg/datagatherer/k8sdiscovery/discovery.go.
Concrete failure: a tenant onboards one authn-jwt authenticator and installs the agent on ten clusters with the wizard's Helm command, which omits cluster_name. All ten upload cluster_name: "disco-agent". In Discovery and Context they become ten identically named clusters. That is arguably worse than the current empty name, because a name that looks deliberately chosen gives the operator no signal that a fallback fired, whereas an unnamed cluster does.
If the wizard genuinely mints a per-cluster service ID, please say so here and cite it, because the chart documentation currently says the opposite and one of the two needs correcting.
There was a problem hiding this comment.
Verified against the actual wizard code (not just the chart README), since this determines whether the fallback is safe:
RegisterKubernetesAgentWizard.tsx:79:--set config.cyberark.serviceId=${wizardData.clusterUuid ?? ''}useRegisterCluster.ts:14-17,52-53: every Conjur identity built from that sameclusterUuid—conjur/authn-jwt/${clusterUuid}/apps,data/.../${clusterUuid}/workloads/...— a distinct authn-jwt authenticator and policy branch per cluster registration, not one shared per tenant.
So two clusters onboarded by the same tenant get different clusterUuid values and thus different serviceIds — the wizard flow this fallback exists for is genuinely per-cluster. The chart README's --set config.cyberark.serviceId=disco-agent is a placeholder literal for the generic manual-deploy path, unrelated to what the wizard generates — but you're right that it reads as per-tenant, and that's exactly the kind of thing an operator following the manual path would misread. I've updated the clusterName doc in a follow-up commit to stop implying a fallback that doesn't exist; happy to also clarify the README's per-tenant wording in a separate PR if you'd like, since it's pre-existing and out of scope here.
One caveat I can't verify from code: clusterUuid is operator-typed free text (validated only by validateUuid()), not auto-derived — so per-cluster-uniqueness holds structurally, but depends on the operator actually entering distinct values per cluster. Not a code defect, just worth knowing.
| // typically the cluster UUID) is always set for these | ||
| // installs, so it's a better last resort than an empty name. | ||
| log.Info("Using cyberark.service_id as cluster name because cluster_name, cluster_id, and ARK_USERNAME are all unset; prefer setting cluster_name explicitly", "clusterName", cfg.CyberArk.ServiceID) | ||
| clusterName = cfg.CyberArk.ServiceID |
There was a problem hiding this comment.
Worth confirming against the backend before merging: this changes what existing Conjur JWT installs send, not only what new ones send.
dataupload.Snapshot.ClusterName has no omitempty (internal/cyberark/dataupload/dataupload.go:62), so today every existing Conjur JWT install PUTs "cluster_name": "" once per period. After this change the same installs start PUTting "cluster_name": "<service_id>" on their first upload after upgrade. If the Discovery and Context API upserts the display name keyed on cluster_id, a name an operator set in the UI is silently overwritten, and the only way to opt out is to start setting config.clusterName in the chart.
The other clients deliberately avoid this: pkg/client/client_venafi_cloud.go:195 and pkg/client/client_ngts.go:241 both add the name query parameter only if opts.ClusterName != "", so empty means "leave the name alone". The CyberArk snapshot path has no equivalent guard, which is what turns a config-time default into an unconditional rename.
There was a problem hiding this comment.
Checked this against the actual DCS/FIS persistence path, since it's the right question:
- FIS:
origin_store_processor.py:130-140buildsOriginStore(name=str(global_metadata.cluster_name), ...)straight from the snapshot, no transform. - DCS:
aurora_postgres_db_handler_origin_stores.py:208-236->make_insert_stmt(aurora_postgres_db_utils.py:142-158) doesstmt.on_conflict_do_update(set_={col: excluded[col] for col in payload_cols}, where=changed)—nameis inpayload_cols, so it's unconditionally overwritten on every scan cycle whenever it differs. No first-write-wins, no separate display-name column, no override flag.
So the mechanism you're describing is real: every periodic upload replaces the stored name. However — I searched discoverycontext-frontend for any existing rename/update-name mutation against this field and found none; only read paths exist today. There's no UI feature yet that lets an operator set a name for this to clobber. So the specific failure (operator renames in UI, agent silently reverts it) is currently hypothetical, not a live regression this PR introduces.
What this PR does change for already-deployed Conjur JWT installs: they go from uploading cluster_name: "" every cycle (no fallback existed) to uploading a non-empty fallback value every cycle — which is a net improvement given there's nothing today to overwrite. But your point stands as a real architectural gap: the moment a rename UI ships, this upsert-every-cycle behavior will clobber it, for every client (not just CyberArk — worth someone opening a tracking ticket for the DCS side, since the fix belongs there, not in this fallback chain). I don't think this PR should be blocked on that pre-existing gap, but wanted to give you the grounded answer rather than assert it away. Let me know if you'd still like this held pending backend sign-off.
Replace the "empty ClusterName is unreachable" prose comment with a test that asserts the config-validation gate directly, so a future auth-method change that reopens the empty-cluster_name path fails this test instead of silently regressing. Fix values.yaml's stale clusterName fallback description (claimed a service-account-name fallback that was never implemented in Go) and regenerate README.md/values.schema.json via `make ark-generate`.
Summary
cluster_namefallback chain (cluster_name->cluster_id->ARK_USERNAME-> empty) always resolves empty for Conjur JWT installs: the disco-agent chart never setscluster_id, andARK_USERNAMEdoesn't exist under JWT auth.cyberark.service_id(the Conjur authn-jwt service ID, always set by the CyberArk onboarding wizard) as a final fallback before empty.service_idorARK_USERNAME+ARK_SECRET), an emptyClusterNameis now unreachable for any MachineHub config that passes validation.Test plan
go test ./pkg/agent/...(excluding the pre-existing envtest-gatedTest_ValidateAndCombineConfig_VenafiConnection, unrelated to this change)🤖 Generated with Claude Code