diff --git a/deploy/charts/disco-agent/README.md b/deploy/charts/disco-agent/README.md index 32107f78..8bbc4144 100644 --- a/deploy/charts/disco-agent/README.md +++ b/deploy/charts/disco-agent/README.md @@ -16,24 +16,38 @@ kubectl create ns "$NAMESPACE" || true ### Add credentials to a Secret -You will require tenant details and credentials for the CyberArk Identity Security Platform. -Put them in the following environment variables: +The agent supports **two authentication methods**, selected automatically by +config: + +| Set this | Method used | +|---|---| +| `config.cyberark.serviceId` (Conjur authn-jwt service-id) | **Conjur JWT exchange** — exchanges a projected ServiceAccount token for a short-lived Conjur access token. No stored password. Preferred for new installs. | +| `ARK_USERNAME` + `ARK_SECRET` in the Secret (and no `serviceId`) | **Legacy CyberArk Identity username/password** — backward compatible with existing GA installs. | + +If **both** are set, the Conjur `serviceId` wins (so a migrating install can add +the service-id before removing its old credentials) and a warning is logged. If +**neither** is set, the agent fails closed at startup. + +The only credential always required in the Kubernetes Secret is the CyberArk +tenant subdomain (`ARK_SUBDOMAIN`). ```sh -export ARK_SUBDOMAIN= # your CyberArk tenant subdomain e.g. tlskp-test -export ARK_USERNAME= # your CyberArk username -export ARK_SECRET= # your CyberArk password -# OPTIONAL: the URL for the CyberArk Discovery API if not using the production environment +export ARK_SUBDOMAIN= # your CyberArk tenant subdomain, e.g. tlskp-test +# OPTIONAL: Discovery API URL for non-production environments export ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/ ``` -Create a Secret containing the tenant details and credentials: +Create the Secret: ```sh +# Production (no ARK_DISCOVERY_API override needed): +kubectl create secret generic agent-credentials \ + --namespace "$NAMESPACE" \ + --from-literal=ARK_SUBDOMAIN=$ARK_SUBDOMAIN + +# Non-production, targeting a non-default Discovery API: kubectl create secret generic agent-credentials \ --namespace "$NAMESPACE" \ - --from-literal=ARK_USERNAME=$ARK_USERNAME \ - --from-literal=ARK_SECRET=$ARK_SECRET \ --from-literal=ARK_SUBDOMAIN=$ARK_SUBDOMAIN \ --from-literal=ARK_DISCOVERY_API=$ARK_DISCOVERY_API ``` @@ -49,23 +63,67 @@ metadata: namespace: cyberark type: Opaque stringData: - ARK_SUBDOMAIN: $ARK_SUBDOMAIN # your CyberArk tenant subdomain e.g. tlskp-test - ARK_SECRET: $ARK_SECRET # your CyberArk password - ARK_USERNAME: $ARK_USERNAME # your CyberArk username - # OPTIONAL: the URL for the CyberArk Discovery API if not using the production environment + ARK_SUBDOMAIN: "tlskp-test" # your CyberArk tenant subdomain + # OPTIONAL: uncomment for non-production Discovery API # ARK_DISCOVERY_API: https://platform-discovery.integration-cyberark.cloud/ + # LEGACY (only if NOT using Conjur serviceId) — username/password auth: + # ARK_USERNAME: "svc-agent@tenant" + # ARK_SECRET: "" ``` -### Deploy the agent +### Configure Conjur JWT authentication + +> Skip this section if you are using the legacy username/password method +> (set `ARK_USERNAME`/`ARK_SECRET` in the Secret and leave `serviceId` empty). + +Set `config.cyberark.serviceId` to the authn-jwt authenticator service ID +configured for this cluster in your Conjur tenant. This is the **bare service-id +segment** (e.g. `disco-agent`), NOT the policy path `conjur/authn-jwt/disco-agent` +— the agent builds the authenticate URL as +`/authn-jwt///authenticate`, so a path here would +double the `conjur/authn-jwt` prefix. The remaining defaults are correct for +CyberArk-hosted tenants: + +| Value | Default | Description | +|---|---|---| +| `config.cyberark.serviceId` | `""` | Conjur authn-jwt service ID (required). Example: `disco-agent` | +| `config.cyberark.account` | `conjur` | Conjur account name. Always `conjur` for CyberArk-hosted tenants. | +| `config.cyberark.jwtSource` | `file` | Token source. `file` = projected SA-token volume (default). `spiffe` deferred. | + +When `config.cyberark.serviceId` is set and `jwtSource` is `file` (the +default), the chart automatically renders a projected ServiceAccount token +volume (audience=`conjur`, expiry 600 s) and mounts it at the fixed path the +agent expects (`/var/run/secrets/tokens/jwt`) — this path isn't configurable, +so there's one fewer way to misconfigure it. No manual volume configuration +is required. + +### Per-tenant Conjur onboarding + +Before deploying the agent, the target tenant must be onboarded in Conjur +Cloud: an `authn-jwt` authenticator scoped to the cluster's OIDC issuer and +JWKS, a registered workload for the agent's ServiceAccount, and the grants that +let that workload authenticate and upload. Onboarding is performed through the +CyberArk web console — see the product documentation for the current +walkthrough. + +Onboarding needs only the tenant administrator's own Conjur Cloud credentials. +The agent itself holds no Conjur identity beyond its projected ServiceAccount +token, and nothing in this chart requires a Conjur admin credential at deploy +time. + +Once onboarding is complete, note the authenticator's service ID — that is the +value for `config.cyberark.serviceId` below. -Deploy the agent: +### Deploy the agent ```sh helm upgrade agent "oci://${OCI_BASE}/charts/disco-agent" \ --install \ --create-namespace \ --namespace "$NAMESPACE" \ - --set fullnameOverride=disco-agent + --set fullnameOverride=disco-agent \ + --set config.cyberark.serviceId=disco-agent \ + --set acceptTerms=true ``` ### Troubleshooting @@ -80,6 +138,14 @@ Check the logs: kubectl logs deployments/disco-agent --namespace "${NAMESPACE}" --follow ``` +#### Conjur authentication errors + +| Symptom | Likely cause | Fix | +|---|---|---| +| Agent logs `401 Unauthorized` from Conjur | ServiceAccount token `audience` does not match the authenticator's configured `audience` value, or the authn-jwt authenticator is not enabled for the account | The chart's projected volume always requests `audience=conjur` — this is fixed, not a values.yaml setting. Confirm the Conjur `conjur/authn-jwt//audience` variable is also set to `conjur`, and that the authenticator is enabled (`CONJUR_AUTHENTICATORS` includes `authn-jwt/`) | +| Agent logs `403 Forbidden` from the upload API | The agent's workload is authenticated but not authorized to upload | Confirm in the CyberArk console that this cluster's workload was granted the uploader permission during onboarding | +| Agent logs `500` / no upload attempt | Conjur is unreachable or returned an unexpected error | Check network policy / DNS; inspect Conjur audit logs for the host identity | + ## Values @@ -352,6 +418,32 @@ This description will be associated with the data that the agent uploads to the > ``` Enable sending of Secret values to CyberArk in addition to metadata. Metadata is always sent, but the actual values of Secrets are not sent by default. When enabled, Secret data is encrypted using envelope encryption using a key managed by CyberArk, fetched from the Discovery and Context service. +#### **config.cyberark.serviceId** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +The Conjur authn-jwt authenticator service ID configured for this tenant. Set this to use the Conjur JWT exchange (preferred). Leave empty to use the legacy CyberArk Identity username/password method (ARK_USERNAME/ARK_SECRET in the credentials Secret) for backward compatibility. If both are set, the serviceId (Conjur) wins. NOTE: bare service-id segment (e.g. "disco-agent"), NOT the policy path +"conjur/authn-jwt/disco-agent" — the agent builds the URL as +/authn-jwt///authenticate. +#### **config.cyberark.account** ~ `string` +> Default value: +> ```yaml +> conjur +> ``` + +The Conjur account name. For CyberArk-hosted tenants this is always "conjur". +#### **config.cyberark.jwtSource** ~ `string` +> Default value: +> ```yaml +> file +> ``` + +Token source for Conjur JWT authentication. +"file" — read the token from the chart's own projected SA-token volume + (default; the mount path is fixed, not configurable). +"spiffe" — deferred; not implemented in this POC. #### **authentication.secretName** ~ `string` > Default value: > ```yaml @@ -457,4 +549,3 @@ endpointAdditionalProperties: ``` - diff --git a/deploy/charts/disco-agent/templates/_helpers.tpl b/deploy/charts/disco-agent/templates/_helpers.tpl index 4cd42b1e..ff3a5cc8 100644 --- a/deploy/charts/disco-agent/templates/_helpers.tpl +++ b/deploy/charts/disco-agent/templates/_helpers.tpl @@ -116,3 +116,13 @@ usage through tuple/variable indirection. {{- printf "%s" $defaultReference -}} {{- end -}} {{- end }} + +{{/* +Whether the Conjur JWT projected-token volume is needed: only when +serviceId selects the Conjur JWT auth path AND jwtSource is unset or "file" +(the only supported source in this POC) — a pure username/password install +(serviceId unset) has no use for it. +*/}} +{{- define "disco-agent.conjurJWTEnabled" -}} +{{- and .Values.config.cyberark.serviceId (or (not .Values.config.cyberark.jwtSource) (eq .Values.config.cyberark.jwtSource "file")) -}} +{{- end }} diff --git a/deploy/charts/disco-agent/templates/configmap.yaml b/deploy/charts/disco-agent/templates/configmap.yaml index 3d38b542..3d712ce1 100644 --- a/deploy/charts/disco-agent/templates/configmap.yaml +++ b/deploy/charts/disco-agent/templates/configmap.yaml @@ -10,6 +10,10 @@ data: cluster_name: {{ .Values.config.clusterName | quote }} cluster_description: {{ .Values.config.clusterDescription | quote }} period: {{ .Values.config.period | quote }} + cyberark: + service_id: {{ .Values.config.cyberark.serviceId | quote }} + account: {{ .Values.config.cyberark.account | quote }} + jwt_source: {{ .Values.config.cyberark.jwtSource | quote }} {{- with .Values.config.excludeAnnotationKeysRegex }} exclude-annotation-keys-regex: {{- . | toYaml | nindent 6 }} diff --git a/deploy/charts/disco-agent/templates/deployment.yaml b/deploy/charts/disco-agent/templates/deployment.yaml index 0c98b9a5..63e09ba0 100644 --- a/deploy/charts/disco-agent/templates/deployment.yaml +++ b/deploy/charts/disco-agent/templates/deployment.yaml @@ -58,26 +58,32 @@ spec: valueFrom: fieldRef: fieldPath: spec.nodeName - - name: ARK_USERNAME + - name: ARK_SUBDOMAIN valueFrom: secretKeyRef: name: {{ .Values.authentication.secretName }} - key: ARK_USERNAME - - name: ARK_SECRET + key: ARK_SUBDOMAIN + - name: ARK_DISCOVERY_API valueFrom: secretKeyRef: name: {{ .Values.authentication.secretName }} - key: ARK_SECRET - - name: ARK_SUBDOMAIN + key: ARK_DISCOVERY_API + optional: true + # Legacy CyberArk Identity username/password (backward compatibility). + # Used only when config.cyberark.serviceId is empty; otherwise the + # agent uses the Conjur JWT exchange and ignores these. Optional so + # JWT-only installs need not set them. + - name: ARK_USERNAME valueFrom: secretKeyRef: name: {{ .Values.authentication.secretName }} - key: ARK_SUBDOMAIN - - name: ARK_DISCOVERY_API + key: ARK_USERNAME + optional: true + - name: ARK_SECRET valueFrom: secretKeyRef: name: {{ .Values.authentication.secretName }} - key: ARK_DISCOVERY_API + key: ARK_SECRET optional: true - name: ARK_SEND_SECRET_VALUES value: {{ .Values.config.sendSecretValues | default "false" | quote }} @@ -116,6 +122,11 @@ spec: - name: config mountPath: "/etc/disco-agent" readOnly: true + {{- if eq (include "disco-agent.conjurJWTEnabled" .) "true" }} + - name: conjur-token + mountPath: /var/run/secrets/tokens + readOnly: true + {{- end }} {{- with .Values.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -127,6 +138,15 @@ spec: configMap: name: {{ include "disco-agent.fullname" . }}-config optional: false + {{- if eq (include "disco-agent.conjurJWTEnabled" .) "true" }} + - name: conjur-token + projected: + sources: + - serviceAccountToken: + path: jwt + audience: conjur + expirationSeconds: 600 + {{- end }} {{- with .Values.volumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/deploy/charts/disco-agent/tests/__snapshot__/configmap_test.yaml.snap b/deploy/charts/disco-agent/tests/__snapshot__/configmap_test.yaml.snap index 998d3082..db251a92 100644 --- a/deploy/charts/disco-agent/tests/__snapshot__/configmap_test.yaml.snap +++ b/deploy/charts/disco-agent/tests/__snapshot__/configmap_test.yaml.snap @@ -6,6 +6,10 @@ custom-cluster-description: cluster_name: "" cluster_description: "A cloud hosted Kubernetes cluster hosting production workloads.\n\nteam: team-1\nemail: team-1@example.com\npurpose: Production workloads\n" period: "12h0m0s" + cyberark: + service_id: "" + account: "conjur" + jwt_source: "file" data-gatherers: - kind: oidc name: ark/oidc @@ -165,6 +169,10 @@ custom-cluster-name: cluster_name: "cluster-1 region-1 cloud-1 " cluster_description: "" period: "12h0m0s" + cyberark: + service_id: "" + account: "conjur" + jwt_source: "file" data-gatherers: - kind: oidc name: ark/oidc @@ -324,6 +332,10 @@ custom-period: cluster_name: "" cluster_description: "" period: "1m" + cyberark: + service_id: "" + account: "conjur" + jwt_source: "file" data-gatherers: - kind: oidc name: ark/oidc @@ -483,6 +495,10 @@ defaults: cluster_name: "" cluster_description: "" period: "12h0m0s" + cyberark: + service_id: "" + account: "conjur" + jwt_source: "file" data-gatherers: - kind: oidc name: ark/oidc diff --git a/deploy/charts/disco-agent/values.schema.json b/deploy/charts/disco-agent/values.schema.json index 401b11a1..a6f17f32 100644 --- a/deploy/charts/disco-agent/values.schema.json +++ b/deploy/charts/disco-agent/values.schema.json @@ -124,6 +124,9 @@ "clusterName": { "$ref": "#/$defs/helm-values.config.clusterName" }, + "cyberark": { + "$ref": "#/$defs/helm-values.config.cyberark" + }, "excludeAnnotationKeysRegex": { "$ref": "#/$defs/helm-values.config.excludeAnnotationKeysRegex" }, @@ -149,6 +152,36 @@ "description": "A human readable name for the cluster where the agent is deployed (optional).\n\nThis cluster name will be associated with the data that the agent uploads to the Discovery and Context service. If empty (the default), the service account name will be used instead.", "type": "string" }, + "helm-values.config.cyberark": { + "additionalProperties": false, + "properties": { + "account": { + "$ref": "#/$defs/helm-values.config.cyberark.account" + }, + "jwtSource": { + "$ref": "#/$defs/helm-values.config.cyberark.jwtSource" + }, + "serviceId": { + "$ref": "#/$defs/helm-values.config.cyberark.serviceId" + } + }, + "type": "object" + }, + "helm-values.config.cyberark.account": { + "default": "conjur", + "description": "The Conjur account name. For CyberArk-hosted tenants this is always \"conjur\".", + "type": "string" + }, + "helm-values.config.cyberark.jwtSource": { + "default": "file", + "description": "Token source for Conjur JWT authentication.\n\"file\" — read the token from the chart's own projected SA-token volume\n (default; the mount path is fixed, not configurable).\n\"spiffe\" — deferred; not implemented in this POC.", + "type": "string" + }, + "helm-values.config.cyberark.serviceId": { + "default": "", + "description": "The Conjur authn-jwt authenticator service ID configured for this tenant. Set this to use the Conjur JWT exchange (preferred). Leave empty to use the legacy CyberArk Identity username/password method (ARK_USERNAME/ARK_SECRET in the credentials Secret) for backward compatibility. If both are set, the serviceId (Conjur) wins. NOTE: bare service-id segment (e.g. \"disco-agent\"), NOT the policy path\n\"conjur/authn-jwt/disco-agent\" — the agent builds the URL as\n/authn-jwt///authenticate.", + "type": "string" + }, "helm-values.config.excludeAnnotationKeysRegex": { "default": [], "description": "You can configure the agent to exclude some annotations or labels from being pushed . All Kubernetes objects are affected. The objects are still pushed, but the specified annotations and labels are removed before being pushed.\n\nDots is the only character that needs to be escaped in the regex. Use either double quotes with escaped single quotes or unquoted strings for the regex to avoid YAML parsing issues with `\\.`.\n\nExample: excludeAnnotationKeysRegex: ['^kapp\\.k14s\\.io/original.*']", diff --git a/deploy/charts/disco-agent/values.yaml b/deploy/charts/disco-agent/values.yaml index 7f362328..38c6ee22 100644 --- a/deploy/charts/disco-agent/values.yaml +++ b/deploy/charts/disco-agent/values.yaml @@ -202,6 +202,30 @@ config: # a key managed by CyberArk, fetched from the Discovery and Context service. sendSecretValues: true + # CyberArk Conjur JWT authentication settings. + # The agent exchanges a projected ServiceAccount token (audience=conjur) for a + # short-lived Conjur access token, then uses that token to authenticate to the + # Discovery & Context upload API. + cyberark: + # The Conjur authn-jwt authenticator service ID configured for this tenant. + # Set this to use the Conjur JWT exchange (preferred). Leave empty to use the + # legacy CyberArk Identity username/password method (ARK_USERNAME/ARK_SECRET + # in the credentials Secret) for backward compatibility. If both are set, the + # serviceId (Conjur) wins. + # NOTE: bare service-id segment (e.g. "disco-agent"), NOT the policy path + # "conjur/authn-jwt/disco-agent" — the agent builds the URL as + # /authn-jwt///authenticate. + serviceId: "" + + # The Conjur account name. For CyberArk-hosted tenants this is always "conjur". + account: "conjur" + + # Token source for Conjur JWT authentication. + # "file" — read the token from the chart's own projected SA-token volume + # (default; the mount path is fixed, not configurable). + # "spiffe" — deferred; not implemented in this POC. + jwtSource: file + authentication: secretName: agent-credentials