Continuous Integration (CI) systems like Jenkins often use long-lived credentials to authenticate to Kubernetes clusters. While some CI systems have native support for issuing short-lived OpenID Connect (OIDC) tokens, Jenkins doesn’t have this native capability. However, there exists the great “OpenID Connect Provider” plugin to get this functionality in Jenkins. The “OpenID Connect Provider” plugin is a plugin that allows Jenkins pipelines to authenticate against any OIDC service. In this blog post, I will outline how to configure and use the Jenkins OIDC plugin in a Kubernetes environment, similar to my previous blog post on using “Using GitHub Actions OpenID Connect in Kubernetes”.
Following diagram shows the authentication flow, which we want to archive:
In the following section, I will outline how you configure OIDC trust in Kubernetes and assign permissions to the OIDC identity. I show different options on how your Jenkins pipeline can request identity tokens and use them to execute Kubernetes commands.
First we need to install the OpenID Connect Provider plugin. You can find installation instruction under “How to install” on the plugin page.
After installing it, you can change global configuration inside your global security settings like the token lifetime or adjusting the token template (e.g. the subject (`sub`) or other values inside the token).
To actually use the OIDC credentials, we need to create them inside of Jenkins. For this add a new credential and select as type the newly available `OpenID Connect ID Token`.
Set following values:
You can use this Jenkins credential like any other Jenkins credential. The main difference is that each time a pipeline accesses this credential, a new token with a limited lifetime is generated unlike static credentials.
First, the Kubernetes API server must trust your Jenkins server as an OIDC identity provider. For this, configure the trust in the Kubernetes API server using the `--oidc`-flags.
--oidc-issuer-url=https://jenkins.example.com/oidc --oidc-client-id=my-kubernetes-cluster --oidc-username-claim=sub --oidc-username-prefix=jenkins-oidc:
With this the OIDC trust inside your Kubernetes API server is configured and your pipeline can use the Jenkins issued identity tokens to authenticate against the Kubernetes API server. Kubernetes extracts the user information from the identity token and uses the mapped Kubernetes username to determine authorization.
Note: Multiple OIDC-issuers, e.g., separate ones for user accounts and automation, can’t be configured in the Kubernetes API server. However, the Gardener project provides a “Webhook Authenticator for dynamic registration of OpenID Connect providers”, which you can deploy inside a generic Kubernetes cluster.
If you have a Gardener Kubernetes cluster, the OIDC webhook authenticator exists as well as a managed shoot service, and you can enable it with adding `.spec.extensions[].type: shoot-oidc-service` to your shoot configuration YAML.
With the OIDC Webhook Authenticator, you can create an OpenIDConnect resource to establish the trust relationship.
apiVersion: authentication.gardener.cloud/v1alpha1
kind: OpenIDConnect
metadata:
name: jenkins-oidc
spec:
issuerURL: https://jenkins.example.com/oidc
clientID: my-kubernetes-cluster
usernameClaim: sub
usernamePrefix: "jenkins-oidc:"
Authorizations via roles and role bindings are required in Kubernetes for any user to perform any action. Following the principle of least privilege to provide the best security, roles should have as few permissions as possible.
The deployment of the demo application only requires permission to modify deployments and list pods.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: demo
name: jenkins-oidc-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "watch", "list", "create", "update", "delete"]
After you create the role, bind it to the mapped workload user. The username consists of the `username-prefix` followed by the extracted `username-claim` attribute from the identity token.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jenkins-oidc-binding
namespace: demo
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: jenkins-oidc-role
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: jenkins-oidc:https://jenkins.example.com/job/oidc-test/
The pipeline identity now has permission to perform actions inside the specific Kubernetes namespace.
With the “Kubernetes” plugin installed you can directly use the credential to access your cluster as you would normally with long-lived credentials.
The pipeline file can then look like below. Make sure to replace the `<API Server address>`, `<Jenkins OIDC credential ID>` and `<API Server CA data>` with your own values.
pipeline { agent any stages { stage('deploy to Kubernetes') { steps { kubeconfig( serverUrl: '<API Server address>', credentialsId: '<Jenkins OIDC credential ID>', caCertificate: '<API Server CA data>' ){ sh ''' # show permissions of current user kubectl auth can-i --list
# create deployment kubectl create deployment hello-oidc --image=k8s.gcr.io/echoserver:1.4
# get pods kubectl get pods ''' } } } } }
To summarize, there are only three steps required to move from long-lived service accounts credentials to leveraging Jenkins OpenID Connect together in Kubernetes:
With an initial effort to set it up, using OIDC allows you to ditch credentials inside your Jenkins pipeline entirely and makes your CI/CD pipelines easier to manage and to keep secure.
Please share your experience and comments below. Have a fantastic day, and happy hacking!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.