Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 127 additions & 18 deletions deploy/charts/disco-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,36 @@ 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
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
--from-literal=ARK_SUBDOMAIN=$ARK_SUBDOMAIN
# Add the optional key only if targeting a non-production Discovery API:
# kubectl patch secret agent-credentials -n "$NAMESPACE" \
# --type=json -p '[{"op":"add","path":"/data/ARK_DISCOVERY_API","value":"'"$(echo -n $ARK_DISCOVERY_API | base64)"'"}]'
```

Alternatively, use the following Secret as a template:
Expand All @@ -49,23 +59,114 @@ 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: "<password>"
```

### 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
`<base>/authn-jwt/<serviceId>/<account>/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: `conjur/authn-jwt/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. |
| `config.cyberark.jwtFilePath` | `/var/run/secrets/tokens/jwt` | Path to the projected token file. Auto-mounted by the chart when `jwtSource=file`. |

The chart automatically renders a projected ServiceAccount token volume
(audience=`conjur`, expiry 600 s) and mounts it at `/var/run/secrets/tokens`
when `config.cyberark.jwtSource` is `file` (the default). No manual volume
configuration is required.

### Per-tenant Conjur onboarding

Before deploying the agent against a new tenant, complete the following steps
in the Conjur tenant:

1. **Enable the authn-jwt authenticator** with `audience=conjur` and
`token-app-property=sub`.

```yaml
# conjur-authn-jwt-policy.yml
- !policy
id: conjur/authn-jwt/disco-agent
body:
- !webservice

- !variable jwks-uri
- !variable token-app-property
- !variable issuer
- !variable audience

- !group hosts
- !permit
role: !group hosts
privilege: [ read, authenticate ]
resource: !webservice
```

2. **Set the authenticator variables** (values shown as examples):

```sh
conjur variable set -i conjur/authn-jwt/disco-agent/token-app-property -v sub
conjur variable set -i conjur/authn-jwt/disco-agent/audience -v conjur
conjur variable set -i conjur/authn-jwt/disco-agent/issuer -v https://kubernetes.default.svc.cluster.local
conjur variable set -i conjur/authn-jwt/disco-agent/jwks-uri -v https://kubernetes.default.svc.cluster.local/openid/v1/jwks
```

3. **Pre-create a Conjur host** for the agent ServiceAccount. The `id` must
match the Kubernetes ServiceAccount's `sub` claim
(`system:serviceaccount:<namespace>:<sa-name>`):

```yaml
# conjur-agent-host-policy.yml
- !host
id: system:serviceaccount/cyberark/disco-agent
annotations:
authn-jwt/disco-agent/sub: system:serviceaccount/cyberark/disco-agent
```

4. **Add the host to the `data/disco/snapshot-uploaders` group** so the
authorizer grants it upload access:

```yaml
- !grant
role: !group data/disco/snapshot-uploaders
member: !host system:serviceaccount/cyberark/disco-agent
```

5. **Add the host to the authn-jwt authenticator's hosts group**:

```yaml
- !grant
role: !group conjur/authn-jwt/disco-agent/hosts
member: !host system:serviceaccount/cyberark/disco-agent
```

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
Expand All @@ -80,6 +181,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 | Confirm `audience=conjur` in both the projected volume (chart default) and the Conjur `conjur/authn-jwt/<serviceId>/audience` variable; ensure the authenticator is enabled (`CONJUR_AUTHENTICATORS` includes `authn-jwt/<serviceId>`) |
| Agent logs `403 Forbidden` from the upload API | The agent's Conjur host is not a member of `data/disco/snapshot-uploaders` | Add the host to the group per step 4 of the onboarding runbook above |
| 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

<!-- AUTO-GENERATED -->
Expand Down
36 changes: 28 additions & 8 deletions deploy/charts/disco-agent/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -116,6 +122,11 @@ spec:
- name: config
mountPath: "/etc/disco-agent"
readOnly: true
{{- if or (not .Values.config.cyberark.jwtSource) (eq .Values.config.cyberark.jwtSource "file") }}
- name: conjur-token
mountPath: /var/run/secrets/tokens
readOnly: true
{{- end }}
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
Expand All @@ -127,6 +138,15 @@ spec:
configMap:
name: {{ include "disco-agent.fullname" . }}-config
optional: false
{{- if or (not .Values.config.cyberark.jwtSource) (eq .Values.config.cyberark.jwtSource "file") }}
- name: conjur-token
projected:
sources:
- serviceAccountToken:
path: jwt
audience: conjur
expirationSeconds: 600
{{- end }}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
42 changes: 42 additions & 0 deletions deploy/charts/disco-agent/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -149,6 +152,45 @@
"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,
"description": "CyberArk Conjur JWT authentication settings. The agent exchanges a projected ServiceAccount token (audience=conjur) for a short-lived Conjur access token used to authenticate to the Discovery & Context upload API.",
"properties": {
"account": {
"$ref": "#/$defs/helm-values.config.cyberark.account"
},
"jwtFilePath": {
"$ref": "#/$defs/helm-values.config.cyberark.jwtFilePath"
},
"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.jwtFilePath": {
"default": "/var/run/secrets/tokens/jwt",
"description": "Path to the projected ServiceAccount token file. The chart auto-mounts a projected volume at this path when jwtSource is \"file\".",
"type": "string"
},
"helm-values.config.cyberark.jwtSource": {
"default": "file",
"description": "Token source for Conjur JWT authentication. \"file\" reads the token from jwtFilePath (projected SA-token volume). \"spiffe\" is deferred and 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. Example: conjur/authn-jwt/disco-agent",
"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.*']",
Expand Down
27 changes: 27 additions & 0 deletions deploy/charts/disco-agent/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,33 @@ 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
# <base>/authn-jwt/<serviceId>/<account>/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 jwtFilePath (projected SA-token volume, default).
# "spiffe" — deferred; not implemented in this POC.
jwtSource: file

# Path to the projected ServiceAccount token file.
# The chart auto-mounts a projected volume at this path when jwtSource is "file".
jwtFilePath: /var/run/secrets/tokens/jwt

authentication:
secretName: agent-credentials

Expand Down
Loading