Skip to content

CP-25864: fall back cluster_name to cyberark.service_id under Conjur JWT - #827

Open
roeezis wants to merge 2 commits into
jetstack:masterfrom
roeezis:CP-25864-cluster-name-fallback
Open

CP-25864: fall back cluster_name to cyberark.service_id under Conjur JWT#827
roeezis wants to merge 2 commits into
jetstack:masterfrom
roeezis:CP-25864-cluster-name-fallback

Conversation

@roeezis

@roeezis roeezis commented Sep 2, 2026

Copy link
Copy Markdown

Summary

  • MachineHub cluster_name fallback chain (cluster_name -> cluster_id -> ARK_USERNAME -> empty) always resolves empty for Conjur JWT installs: the disco-agent chart never sets cluster_id, and ARK_USERNAME doesn't exist under JWT auth.
  • Adds cyberark.service_id (the Conjur authn-jwt service ID, always set by the CyberArk onboarding wizard) as a final fallback before empty.
  • Combined with the existing config-validation gate (requires service_id or ARK_USERNAME+ARK_SECRET), an empty ClusterName is now unreachable for any MachineHub config that passes validation.

Test plan

  • go test ./pkg/agent/... (excluding the pre-existing envtest-gated Test_ValidateAndCombineConfig_VenafiConnection, unrelated to this change)
  • Updated 2 existing tests whose expected behavior changed, added 1 new fallback test, removed 1 now-unreachable "empty" case with explanatory comment

🤖 Generated with Claude Code

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.
Comment thread pkg/agent/config.go
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typically the cluster UUID does not match what this repository tells users about service_id.

  • deploy/charts/disco-agent/values.yaml describes config.cyberark.serviceId as "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 upgrade example in that README passes the literal --set config.cyberark.serviceId=disco-agent.
  • internal/cyberark/client.go:50 hedges 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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 same clusterUuidconjur/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.

Comment thread pkg/agent/config.go
// 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this against the actual DCS/FIS persistence path, since it's the right question:

  • FIS: origin_store_processor.py:130-140 builds OriginStore(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) does stmt.on_conflict_do_update(set_={col: excluded[col] for col in payload_cols}, where=changed)name is in payload_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.

Comment thread pkg/agent/config_test.go Outdated
Comment thread pkg/agent/config.go
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`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants