Skip to content

Conversation

@inteon
Copy link
Contributor

@inteon inteon commented Jan 26, 2026

This PR adds OIDC discovery data upload functionality to the disco-agent.

The data gatherer itself was added in #758.

The following script can be used to test this PR (NOTE: I intentionally made the openid-discovery endpoint fail):

#!/bin/bash

kubectl delete clusterrolebinding system:service-account-issuer-discovery || true

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: read-only-user
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  namespace: default
  name: read-only-role
rules:
- nonResourceURLs:
  - /openid/v1/jwks
  - /openid/v1/jwks/
  verbs:
  - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-only-binding
subjects:
- kind: ServiceAccount
  name: read-only-user
  namespace: default
roleRef:
  kind: ClusterRole
  name: read-only-role
  apiGroup: rbac.authorization.k8s.io
EOF

cat <<EOF > read-only-kubeconfig.yaml
apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority-data: $(kubectl config view --raw -o jsonpath="{.clusters[0].cluster.certificate-authority-data}")
    server: $(kubectl config view --raw -o jsonpath="{.clusters[0].cluster.server}")
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: read-only-user
  name: read-only-context
current-context: read-only-context
users:
- name: read-only-user
  user:
    token: $(kubectl create token read-only-user --duration=24h)
EOF

KUBECONFIG=$(pwd)/read-only-kubeconfig.yaml \
go run . agent \
    --agent-config-file examples/one-shot-oidc.yaml \
    --one-shot \
    --output-path output.json

cat output.json

@inteon inteon force-pushed the oidc_upload branch 6 times, most recently from c140a0e to c74a3bd Compare January 26, 2026 12:08
@inteon inteon requested a review from Copilot January 26, 2026 13:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds OIDC discovery data collection and upload functionality to the disco-agent. The agent now gathers OpenID Connect configuration and JWKS (JSON Web Key Set) information from the Kubernetes API server's well-known OIDC endpoints and uploads this data to the CyberArk backend.

Changes:

  • Introduced a new OIDC data gatherer that fetches OIDC configuration from /.well-known/openid-configuration and JWKS from /openid/v1/jwks endpoints
  • Modified the OIDC data gatherer to return a pointer to OIDCDiscoveryData instead of a value, consistent with other data gatherers
  • Added OIDC fields to the CyberArk snapshot structure for data upload
  • Configured the OIDC gatherer in the default agent deployment templates

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/datagatherer/oidc/oidc.go Changed return type from value to pointer for consistency with other data gatherers
pkg/datagatherer/oidc/oidc_test.go Updated type assertions to handle pointer type
api/datareading.go Added OIDCDiscoveryData type to the unmarshal priority list
pkg/client/client_cyberark.go Added extractOIDCFromReading function and registered it in defaultExtractorFunctions
pkg/client/client_cyberark_test.go Added OIDC test data with error scenarios
internal/cyberark/dataupload/dataupload.go Added OIDC-related fields to the Snapshot struct
examples/machinehub.yaml Added OIDC gatherer configuration example
examples/machinehub/input.json Added example OIDC data with configuration and JWKS
deploy/charts/disco-agent/templates/configmap.yaml Enabled OIDC gatherer in default configuration
deploy/charts/disco-agent/tests/snapshot/configmap_test.yaml.snap Updated test snapshots to include OIDC gatherer

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@inteon inteon added the test-e2e To signal e2e test job to be run label Jan 26, 2026
@inteon inteon closed this Jan 26, 2026
@inteon inteon reopened this Jan 26, 2026
@inteon inteon force-pushed the oidc_upload branch 2 times, most recently from bdc6c8c to d155d1c Compare January 29, 2026 12:48
@maelvls
Copy link
Member

maelvls commented Feb 1, 2026

Hey. I haven't yet looked at the details of the code. I have run the commands you shared in the description and everything worked as advertised when running with Kind.

I've tested with our "devstack" Kubernetes cluster (EKS), and I got the following error:

$ go run . agent \
    --agent-config-file ./mine/minimal-config.yaml \
    --one-shot \
    --output-path /dev/stdout
I0201 21:35:09.151862   84717 run.go:58] "Starting" logger="Run" version="development" commit=""
I0201 21:35:09.152324   84717 config.go:548] "ignoring the venafi-cloud.uploader_id field in the config file. This field is not needed in Local File mode." logger="Run"
I0201 21:35:09.152334   84717 run.go:116] "Healthz endpoints enabled" logger="Run.APIServer" addr=":8081" path="/healthz"
I0201 21:35:09.152340   84717 run.go:120] "Readyz endpoints enabled" logger="Run.APIServer" addr=":8081" path="/readyz"
I0201 21:35:09.152348   84717 run.go:269] "Pod event recorder disabled" logger="Run" reason="The agent does not appear to be running in a Kubernetes cluster." detail="When running in a Kubernetes cluster the following environment variables must be set: POD_NAME, POD_NODE, POD_UID, POD_NAMESPACE"
[
  {
    "data-gatherer": "ark/oidc",
    "timestamp": "2026-02-01T21:35:10+01:00",
    "data": {
      "openid_configuration_error": "failed to get OIDC discovery document: Error from server (Forbidden): unknown",
      "jwks_error": "failed to get JWKS from jwks_uri: Error from server (Forbidden): unknown"
    },
    "schema_version": "v2.0.0"
  }
]I0201 21:35:10.946392   84717 client_file.go:35] "Data saved to local file" logger="Run.gatherAndOutputData.postData" outputPath="/dev/stdout"
I0201 21:35:10.946412   84717 run.go:420] "Data sent successfully" logger="Run.gatherAndOutputData.postData"

Is that because the .well-known/openid-configuration path isn't available by default to anyone on EKS? I can confirm that I can't even read it as a dev:

$ kubectl get --raw /.well-known/openid-configuration
Error from server (Forbidden): forbidden: User "KubernetesDevelopers" cannot get path "/.well-known/openid-configuration"

It seems like the API server itself doesn't serve /.well-known/openid-configuration on EKS clusters. I requested a JWT token and inspected it to know what the iss was:

$ k create token --audience=test default | step crypto jwt inspect --insecure
{
  "payload": {
    "aud": [
      "test"
    ],
    "exp": 1769982431,
    "iat": 1769978831,
    "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/84750CA2848D7E73C09B72E1BE9DF8E4",
    "jti": "833def70-4cb0-478b-9c83-c6bf920cfc5b",
    "kubernetes.io": {
      "namespace": "dev210",
      "serviceaccount": {
        "name": "default",
        "uid": "51ac12c7-c290-4759-984c-27dbd221cd51"
      }
    },
    "nbf": 1769978831,
    "sub": "system:serviceaccount:dev210:default"
  }
}

From there, I was able to query the /.well-known/openid-configuration endpoint:

$ curl https://oidc.eks.us-west-2.amazonaws.com/id/84750CA2848D7E73C09B72E1BE9DF8E4/.well-known/openid-configuration | jq
{
  "issuer": "https://oidc.eks.us-west-2.amazonaws.com/id/84750CA2848D7E73C09B72E1BE9DF8E4",
  "jwks_uri": "https://oidc.eks.us-west-2.amazonaws.com/id/84750CA2848D7E73C09B72E1BE9DF8E4/keys",
  "authorization_endpoint": "urn:kubernetes:programmatic_authorization",
  "response_types_supported": [
    "id_token"
  ],
  "subject_types_supported": [
    "public"
  ],
  "claims_supported": [
    "sub",
    "iss"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ]
}

Not sure how that would work in OpenShift: will the Venafi Kubernetes Agent be able to read /.well-known/openid-configuration? My assumption is that the RBAC doesn't allow it by default (https://access.redhat.com/solutions/7046347) and that the cluster admin would have to apply the following RBAC:

kubectl create clusterrolebinding oidc-reviewer \
  --clusterrole=system:service-account-issuer-discovery \
  --group=system:unauthenticated

Red Hat does not see an immediate security risk, when granting system:service-account-issuer-discovery ClusterRole to system:unauthenticated Group. Red Hat though is not planning on granting such permissions to unauthenticated users by default as Red Hat OpenShift Container Platform 4 - Cluster administrators should carefully evaluate the risk of exposing those endpoints to unauthenticated users on a case by case basis, factoring in their organizational security requirements and based on a risk assessment for the specific Red Hat OpenShift Container Platform 4 - Cluster.

I think it would be wise to document what the procedure would be to access these endpoints in each scenario (OpenShift and EKS, to start with).

I also remember that we had issues with OpenShift users that would be given an in-cluster-only URL as jwks_uri. But since we are requesting both /.well-known/openid-configuration and the jwks_uri from within the cluster, it won't be a problem. (I recall writing on that topic in https://hackmd.io/@maelvls/SkM3ohONp)

@inteon
Copy link
Contributor Author

inteon commented Feb 2, 2026

@maelvls as part of our Helm chart, we already bind the agent's SA to system:service-account-issuer-discovery: https://github.com/jetstack/jetstack-secure/blob/master/deploy/charts/disco-agent/templates/rbac.yaml#L99-L112

@inteon
Copy link
Contributor Author

inteon commented Feb 2, 2026

FIXED: In your example it is clear that kubectl better explains what went wrong: "Error from server (Forbidden): forbidden: User "KubernetesDevelopers" cannot get path "/.well-known/openid-configuration"".

I fixed my code, now it also returns a more informative error message.

@inteon inteon force-pushed the oidc_upload branch 2 times, most recently from 84b6eab to 0f08950 Compare February 2, 2026 10:33
@inteon
Copy link
Contributor Author

inteon commented Feb 2, 2026

I just verified that this works on EKS:

[
  {
    "data-gatherer": "ark/oidc",
    "timestamp": "2026-02-02T14:56:27+01:00",
    "data": {
      "openid_configuration": {
        "id_token_signing_alg_values_supported": [
          "RS256"
        ],
        "issuer": "https://oidc.eks.us-east-1.amazonaws.com/id/39C19841A4D3DDA53FF039A2FDB47F1F",
        "jwks_uri": "https://oidc.eks.us-east-1.amazonaws.com/id/39C19841A4D3DDA53FF039A2FDB47F1F/keys",
        "response_types_supported": [
          "id_token"
        ],
        "subject_types_supported": [
          "public"
        ]
      },
      "jwks": {
        "keys": [
          {
            "alg": "RS256",
            "e": "AQAB",
            "kid": "d5ee057693d2f0189434b26a2ad700de6d12d18f",
            "kty": "RSA",
            "n": "zUnlCaOtpGrUk14-kJ3uw7I-ud51IXA-pBFN3bOJsyDn4xUePnnSArjgwjgcfLOhrWVfNO-QvU3Aa19CeCGZaQzz2kTDVVVFZA7-r57UTEDefjJcpzIGde5SnzYv3-Up68dF4ZQHyrvc-CkTW5NfGbnKC4g5-RPBl5fzvvivsaNg_alKqp5RwPT0SFNE3ObyvM9-Qi_7E0wYIM8Z3oGeHwHVJrtWZ8AkP_0WTOtltl6D17Dn90r2Fc84puIPSYWMGlUyuf7RF1xIE6lfNS9leChH_1xuMX2CDjbm2XM2n8TMnB8laT7LASk_xpQIVQMz0qwiPAUaxyjfPYQg_LwU1w",
            "use": "sig"
          },
          {
            "alg": "RS256",
            "e": "AQAB",
            "kid": "d5ee057693d2f0189434b26a2ad700de6d12d18f",
            "kty": "RSA",
            "n": "zUnlCaOtpGrUk14-kJ3uw7I-ud51IXA-pBFN3bOJsyDn4xUePnnSArjgwjgcfLOhrWVfNO-QvU3Aa19CeCGZaQzz2kTDVVVFZA7-r57UTEDefjJcpzIGde5SnzYv3-Up68dF4ZQHyrvc-CkTW5NfGbnKC4g5-RPBl5fzvvivsaNg_alKqp5RwPT0SFNE3ObyvM9-Qi_7E0wYIM8Z3oGeHwHVJrtWZ8AkP_0WTOtltl6D17Dn90r2Fc84puIPSYWMGlUyuf7RF1xIE6lfNS9leChH_1xuMX2CDjbm2XM2n8TMnB8laT7LASk_xpQIVQMz0qwiPAUaxyjfPYQg_LwU1w",
            "use": "sig"
          }
        ]
      }
    },
    "schema_version": "v2.0.0"
  }
]

If you try again with the latest version of this PR, you should get a better error message explaining why it did not work for you.

Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
},
expOIDCConfigError: `failed to get /.well-known/openid-configuration: Error from server (Forbidden): forbidden: User "system:serviceaccount:default:test" cannot get path "/.well-known/openid-configuration"`,
expJWKSError: `failed to get /openid/v1/jwks: Error from server (Forbidden): forbidden: User "system:serviceaccount:default:test" cannot get path "/openid/v1/jwks"`,
},
Copy link
Member

Choose a reason for hiding this comment

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

I was about to say that it might be worth adding a test case where /.well-known/openid-configuration succeeds but /openid/v1/jwks fails, but it's not really needed as both fetches are performed regardless of the result of the other.

@maelvls
Copy link
Member

maelvls commented Feb 2, 2026

I retried, the error message is much more descriptive:

$ cat mine/minimal-config.yaml
cluster_name: "kind-mael"
cluster_description: "Kind cluster on Mael's Aorus home machine"
server: "https://api.venafi.cloud/"
venafi-cloud:
  uploader_id: "no"
  upload_path: "/v1/tlspk/upload/clusterdata"
data-gatherers:
  - kind: oidc
    name: ark/oidc
period: 3m

$ go run . agent \
    --agent-config-file ./mine/minimal-config.yaml \
    --one-shot \
    --output-path /dev/stdout
[
  {
    "data-gatherer": "ark/oidc",
    "timestamp": "2026-02-02T16:49:20+01:00",
    "data": {
      "openid_configuration_error": "failed to get /.well-known/openid-configuration: Error from server (Forbidden): forbidden: User \"KubernetesDevelopers\" cannot get path \"/.well-known/openid-configuration\"",
      "jwks_error": "failed to get /openid/v1/jwks: Error from server (Forbidden): forbidden: User \"KubernetesDevelopers\" cannot get path \"/openid/v1/jwks\""
    },
    "schema_version": "v2.0.0"
  }
]

Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test-e2e To signal e2e test job to be run

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants