Skip to content

feat(agent): add SMS/Conjur JWT authentication alongside username/password - #817

Draft
roeezis wants to merge 2 commits into
jetstack:masterfrom
roeezis:public/CP-21164-sms-conjur-jwt
Draft

feat(agent): add SMS/Conjur JWT authentication alongside username/password#817
roeezis wants to merge 2 commits into
jetstack:masterfrom
roeezis:public/CP-21164-sms-conjur-jwt

Conversation

@roeezis

@roeezis roeezis commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Add a workload-identity JWT auth path to the DisCo agent, alongside the existing CyberArk Identity username/password login — not replacing it. Both methods coexist and the agent auto-selects by config. The agent reads a projected ServiceAccount token, exchanges it at Secrets Manager SaaS (Conjur Cloud) authn-jwt for a short-lived Conjur access token, and presents it as a Bearer credential. Only the token source changes, behind the existing identity.RequestAuthenticator seam; dataupload / service discovery / upload pipeline untouched.

Auth-method selection (backward compatibility)

config.cyberark.serviceId set?          -> Conjur JWT exchange      [preferred]
else ARK_USERNAME + ARK_SECRET present?  -> legacy username/password [GA installs]
else                                     -> fail closed at startup
both set                                 -> serviceId wins (warn, ignore UP)

serviceId-wins lets a migrating install add the service-id before removing old creds — no rollout window with no method. Existing GA installs (UP creds, no serviceId) keep working with zero config change.

What changed

  • internal/cyberark/conjur/ — authn-jwt exchange, short-lived token cache, sets Bearer. Implements RequestAuthenticator. Requests the base64-encoded access token explicitly (Conjur's canonical wire form).

  • internal/cyberark/jwtsource/file source (projected SA token, default /var/run/secrets/tokens/jwt); spiffe deferred.

  • internal/cyberark/identity/username_password.go — legacy UP-login retained, behaviour unchanged.

  • internal/cyberark/client.goselectAuthenticator picks the method by config. account="conjur" confirmed correct (was a POC placeholder — verified against a live tenant).

  • internal/envelope/keyfetch/ — JWKS fetch uses the same authenticator selector.

  • pkg/agent/config.go — MachineHub validation no longer hard-requires service_id; runtime fails closed if no method configured.

  • helm — optional ARK_USERNAME/ARK_SECRET env alongside the projected aud=conjur token volume; README + values document selection. config.cyberark.serviceId must be the bare authenticator service-id segment, not the full policy path.

  • hack/onboard-disco-agent.sh (new) — full customer-run onboarding for the Conjur side of this feature, both tenant-level and per-cluster:

    Tenant-level (once per tenant per provider type)
      T1  POST /branches            -> data/disco
      T2  POST /groups              -> data/disco/<type>-uploaders
      T3  POST /secrets/static      -> data/disco/<type>-uploaders-probe
          (no value; group granted read+execute — this is the authorization decision)
    Per-cluster (each onboard)
      C1  POST /branches            -> data/<cluster-UUID>
      C2  POST /branches            -> data/<cluster-UUID>/workloads
      C3  POST /authenticators      -> authn-jwt/<cluster-UUID> (enabled)
      C4  POST /workloads/          -> workload under .../workloads
      C5  POST /groups/<apps>/members       -> workload (authenticate grant)
      C6  POST /groups/<uploaders>/members  -> workload (authorize grant)
    

    Runs entirely with the customer's own Conjur admin token — the agent (and DisCo generally) holds no Conjur identity of its own. Idempotent (409 treated as success), fails closed, supports --dry-run/--skip-deploy.

Authorization

The uploader-group membership check above is what the request-time authorizer relies on: the agent probes the value-less secret with its own token via a batch secret-value fetch; any 2xx response means the group grants it access (no secret value is ever needed or stored), anything else denies. This keeps the authorization decision entirely inside Secrets Manager — no new API surface needed there.

Tests

internal/cyberark/... green incl. 4 auth-selection cases (serviceID→conjur, UP→identity, both→conjur wins, neither→error) and the retained UP-login tests. pkg/*/keyfetch verified statically (private venafi dep blocks full local build; CI exercises it).

Status

Implemented and validated end-to-end against a live Secrets Manager tenant. Draft for review.

🤖 Generated with Claude Code

roeezisholz and others added 2 commits July 1, 2026 12:57
…sword

Adds a workload-identity auth path: the agent reads a projected
ServiceAccount token, exchanges it at Secrets Manager SaaS (Conjur Cloud)
authn-jwt for a short-lived Conjur access token, and presents it as a
Bearer credential. The existing CyberArk Identity username/password login
is retained; the method is selected automatically by config:

  config.cyberark.serviceId set   -> Conjur JWT exchange (preferred)
  else ARK_USERNAME + ARK_SECRET  -> username/password (unchanged)
  both set                        -> serviceId wins
  neither                         -> fail closed

Only the token source changes, behind the existing
identity.RequestAuthenticator seam; dataupload, service discovery and the
upload pipeline are untouched.

- internal/cyberark/conjur: authn-jwt exchange, ~8m token cache, Bearer
- internal/cyberark/jwtsource: file source (projected SA token); spiffe deferred
- internal/cyberark/identity: username/password login retained (split into
  username_password.go), behaviour unchanged
- internal/cyberark/client.go: selectAuthenticator picks the method;
  NewRequestAuthenticator exposed for envelope/keyfetch
- internal/envelope/keyfetch: JWKS fetch uses the same authenticator selector
- pkg/agent/config.go: config.cyberark.* keys; service_id no longer mandatory
- deploy/charts/disco-agent: projected token volume (aud=conjur), config keys,
  optional ARK_USERNAME/ARK_SECRET; docs

Unit tests cover the four selection cases and the retained username/password
login.
… base64 token

Adds hack/onboard-disco-agent.sh: full onboarding for the DisCo agent's
SMS/Conjur JWT auth, tenant-level (branch, uploader group, value-less
authorization-probe secret) and per-cluster (authenticator, workload,
grants). Runs entirely with the customer's own Conjur Cloud admin
token; DisCo holds no Conjur identity of its own. Idempotent, fails
closed, supports --dry-run/--skip-deploy.

Also: account="conjur" is confirmed correct (was a POC placeholder),
and the authn-jwt exchange now requests the base64-encoded access
token explicitly (Conjur's canonical wire form), matching what the
DisCo authorizer expects.
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