Open Source Blogs
Immerse yourself in SAP open source! Discover collaborative projects, insights into the latest technologies, and best practices in open source development.
cancel
Showing results for 
Search instead for 
Did you mean: 
gabrielt
Product and Topic Expert
Product and Topic Expert
2,106

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:

Diagram modified from the Kubernetes OpenID Connect Tokens diagram licensed under CC BY 4.0.

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.

Installing and Configuring the OIDC Plugin in Jenkins

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).

Creating an OIDC Credential in Jenkins

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:

  • `Issuer`: can stay blank to use the default
  • `Audience`: pick a unique value for your target Kubernetes cluster. A good value is the Kubernetes API server URL.
  • `ID`: choose a reasonable value
  • `Descriptions`: choose a reasonable value

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.

Setting Up OIDC Trust in Kubernetes

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:
  • `issuer-url`: Unique identifier for the OIDC identity provider. In the case of GitHub Actions, this is always `https://jenkins.example.com/oidc`.
  • `client-id`: Unique identifier for the Kubernetes cluster (e.g. your Kubernetes API server URL).
  • username-claim: Identity token attribute to use as a username. It should uniquely represent the pipeline to allow granular authorization. The OIDC plugin sets the pipeline URL like `https://jenkins.example.com/job/oidc-test/` as the default subject (`sub`) in the identity token. For most scenarios, this subject attribute is a good choice.
  • `username-prefix`: The prefix used for all identities issued by this OIDC provider. A unique prefix prevents unwanted impersonation of users inside your Kubernetes cluster.
  • `required-claim`: Multiple key-value pairs restrict which identities have access. You can restrict it to attributes in the token. These available attributes can be configured in the global Jenkins settings. Without any restriction, any pipeline from your Jenkins could access your cluster.

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.

Supporting Multiple Trusted OIDC-Issuers in Kubernetes

 

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:"

Providing Identities Access in Kubernetes

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.

Using the OIDC Token in a Pipeline

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 ''' } } } } }

Conclusion

To summarize, there are only three steps required to move from long-lived service accounts credentials to leveraging Jenkins OpenID Connect together in Kubernetes:

  1. Configure the Kubernetes cluster to trust the Jenkins OIDC issuer
  2. Authorize the pipeline identity with a role binding inside the Kubernetes cluster
  3. Adjust your existing Jenkins pipeline to use the OIDC backed secret to make requests to 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!