Create and use service accounts with Configuration as Code

8 minute readSecurityScalabilityAutomation

You can manage service accounts and their authentication methods, including tokens and OpenID Connect (OIDC) configurations, using Configuration as Code (CasC). CasC allows you to declaratively define service accounts, authentication methods, and group memberships at root and item scope.

Define root-scoped service accounts

Define root-scoped service accounts and group memberships in the CasC bundle’s rbac.yaml file. Service accounts are defined under a serviceAccounts section at the root. Each service account definition includes a name and an authentication list.

Define item-scoped service accounts

Define item-scoped service accounts and group memberships in the CasC bundle’s items.yaml file. Service accounts are defined under a serviceAccounts section under an item definition. Each service account definition includes a name and an authentication list.

Configuration modes in CasC

As with Role-Based Access Control (RBAC) in CasC, you must select either SYNC or UPDATE mode within the rbac.yaml file and within each items.yaml file to specify how service accounts, authentication methods, and group memberships are managed when the configuration is applied.

  • SYNC mode: Ensures that any service accounts, authentication methods, and group memberships not defined in CasC are removed when the configuration is applied.

  • UPDATE mode: Adds or modifies only the service accounts, authentication methods, and group memberships defined in CasC, leaving others unchanged.

Configure token authentication

Add a token to a service account to allow automated systems to authenticate using HTTP Basic authentication. The following example items.yaml file defines an item-scoped service account with one token and adds the service account to a group.

  • CloudBees recommends that you use ${readFile:…​} to read from secure sources like Kubernetes Secrets rather than hardcoding passwords. For details about supported secret sources and access syntax, refer to the Jenkins Configuration as Code documentation for Handling Secrets.

  • The rbac.yaml file is assumed to come from a trusted source and may use interpolation such as ${readFile:…​} to define service account token passwords. However, users with only folder-level permissions can apply items.yaml files, so loading files from the server could be a security risk. Therefore, by default, the items.yaml file cannot use interpolation unless an administrator has explicitly enabled it in the CasC configuration.

removeStrategy: rbac: SYNC(1) items: NONE items: - kind: folder name: my-team serviceAccounts: - name: bot authentication: - type: password(2) id: bot-token(3) description: for CLI access(4) password: ${readFile:/run/secrets/bot-token}(5) expiration: '2027-01-01T00:00:00Z'(6) groups: - name: build-managers roles: - name: build-jobs members: serviceAccounts: - my-team/bot(7)
1 Configuration mode must be SYNC or UPDATE. Refer to Configuration modes in CasC.
2 Authentication type must be password. No other types are supported.
3 Mandatory id attribute uniquely identifies the token.
4 Optional description for the token’s purpose.
5 The password value (whether read from a file via ${readFile:…​} or hardcoded) must begin with a cloudbees_ci_sa_ prefix, followed by exactly 32 hexadecimal characters (for example, cloudbees_ci_sa_abc123def456789012345678901234ef). You can generate compliant tokens using standard tools, such as openssl rand -hex 16 on Linux/macOS.
6 Optional expiration in ISO-8601 format (must be quoted in YAML).
7 Item-scoped service accounts must be referenced by their full path.

Manage token lifecycles in CasC

When applying a CasC bundle, the token behavior depends on whether the token already exists (as identified by id) and whether the password attribute is specified:

Token type password attribute specified password attribute omitted

Existing token

The password attribute is updated. Other attributes (for example, description and expiration) are added, modified, or cleared according to what you specified in the bundle.

The password attribute remains unchanged. Other attributes (for example, description and expiration) are added, modified, or cleared according to what you specified in the bundle.

New token

The token is created with the specified password.

The token is not created, and a warning is issued.

When you export a CasC bundle, all token fields are included except password. This behavior enables token rotation in CasC while allowing the old token to be used temporarily.

Example: Rotate a token with temporary overlap

The following procedure shows how to rotate a token, allowing the old token to work until 2026-06-08.

  1. Start with your initial service account definition:

    serviceAccounts: - name: bot authentication: - type: password id: bot-token password: ${readFile:/run/secrets/bot-token}
  2. Edit the bundle on 2026-06-01, to add the new token and set an expiration on the old token:

    serviceAccounts: - name: bot authentication: - type: password id: bot-token description: deprecated, please switch to new token expiration: '2026-06-08T00:00:00Z' - type: password id: new-bot-token password: ${readFile:/run/secrets/bot-token}
  3. Change the backing Secret to a new value and apply the bundle so that both tokens work for one week.

  4. Update any scripts or tools to use the new token before 2026-06-08.

  5. After 2026-06-08, edit the bundle again to remove the old token:

    serviceAccounts: - name: bot authentication: - type: password id: new-bot-token password: ${readFile:/run/secrets/bot-token}

Configure OIDC authentication

OIDC authentication uses a temporary JSON Web Token (JWT) from an OIDC identity provider (IdP) to authenticate a service account without requiring a long-lived token.

A service account supports multiple OIDC configurations, but CloudBees recommends defining a separate service account for each IdP or system if you want to manage permissions for each independently.

Prerequisites for OIDC authentication

OIDC authentication eliminates the need to manage long-lived tokens and the associated risks. It requires the following:

  • The system or script using the service account must be able to receive a temporary JWT identity token from an OIDC identity provider (IdP).

  • The service account must be configured to expect JWTs from the OIDC IdP, including specific claim values such as issuer, subject, and audience.

  • The IdP must:

    • Be accessible from the CloudBees CI network, so the operations center or controller receiving the authenticated request can contact the IdP to verify the JWT.

    • Support OpenID Connect Discovery. For a JWT with "iss":"https://idp.example.com", the IdP must serve a JSON document at https://idp.example.com/.well-known/openid-configuration containing a jwks_uri field pointing to the public key used to verify the JWT’s signature.

If these conditions are met, OIDC authentication is simpler to manage than tokens, because there are no secrets to store or rotate, and no special lifecycle considerations when applying bundle updates.

Supported systems for OIDC authentication

CloudBees CI supports OIDC authentication from any IdP that meets the prerequisites. The following systems are common, tested examples:

  • One or more controllers with the OpenID Connect Provider plugin installed. These can be CloudBees CI or Jenkins controllers in an unrelated CloudBees CI installation. For usage details, refer to the Jenkins OpenID Connect Provider plugin documentation.

    A controller can also authenticate to itself using OIDC, for example, to modify its own CasC configuration through the REST API from a Pipeline build.
  • GitHub Actions workflows.

  • A Kubernetes Pod using service account token projection.

OIDC authentication for service accounts is machine-to-machine authentication and is distinct from interactive user authentication through a web browser, which is the original use case for OIDC.

Configure the CasC bundle

To configure OIDC authentication for a service account in CasC:

  1. In the operations center rbac.yaml file, configure the list of allowed OIDC issuer domains using the serviceAccountOidcIssuerDomains field:

    removeStrategy: rbac: SYNC(1) serviceAccountOidcIssuerDomains: - idp.example.com(2) - ci.example.com(3)
    1 Configuration mode must be SYNC or UPDATE. Refer to Configuration modes in CasC.
    2 Allows JWTs from idp.example.com and any of its subdomains. Replace with your IdP’s domain. You can list multiple domains to allow JWTs from different issuers.
    3 Allows JWTs from ci.example.com and any of its subdomains. An empty list (or the absence of the field) blocks all OIDC authentication.
  2. In the rbac.yaml or items.yaml file, add a type: oidc entry to the service account’s authentication list:

    removeStrategy: rbac: SYNC(1) serviceAccounts: - name: bot authentication: - type: oidc(2) id: my-idp(3) issuer: https://idp.example.com(4) subject: my-system:my-workflow(5) audience: d9735032-50db-4d10-8deb-99722c9594d2(6)
    1 Configuration mode must be SYNC or UPDATE. Refer to Configuration modes in CasC.
    2 Identifies OIDC authentication type.
    3 Uniquely identifies the OIDC configuration among authentications for this service account.
    4 The iss claim value expected in the JWT, which must match the IdP’s issuer URL. The issuer URL must use the https scheme.
    5 The sub claim value expected in the JWT, as defined by the IdP.
    6 Optional. The aud claim value expected in the JWT. Specify a different value only if your IdP does not allow you to control the aud claim. Defaults to the service account URL if not specified (for example, https://cloudbees-ci.example.com/cjoc/serviceAccounts/bot/).

    If you are unsure what claims your IdP includes in the JWTs it issues, you can run the following command to print the claims without leaking the JWT itself or its signature:

    set +x # make sure the JWT value is not printed in the logs echo $JWT | cut -d. -f2 | base64 -d 2>/dev/null | jq

Once you apply the OIDC configuration, the service account can be authenticated by any system that can obtain a JWT from the IdP with the expected claims and present it to the operations center or controller. The operations center or controller verify the JWT’s signature using the public key obtained from the IdP’s jwks_uri and then verify that the claims match the expected values defined in CasC.

OIDC usage examples

The following examples show how to authenticate to a CloudBees CI service account using OIDC from various systems. Refer to your IdP documentation to determine exactly how to obtain a JWT with the expected claims.

Only Basic authentication is supported for service accounts. The JWT must be passed as the password when authenticating with the service account, just as you would a token, with the username set to the service account name. The JWT cannot be passed in an Authorization: Bearer header.

Jenkins upstream example

To authenticate from a Jenkins controller to a CloudBees CI service account using OIDC, install and use the OpenID Connect Provider plugin on the Jenkins controller to obtain a JWT and present it to the CloudBees CI REST API or CLI. The issuer is derived from the Jenkins controller URL, which is shown when you create the ID_TOKEN credentials, and the subject is the job URL.

CloudBees recommends setting the audience to the service account URL when configuring the ID_TOKEN credentials, in which case the audience setting may be omitted from CasC.

The following example shows Scripted Pipeline usage:

curl -sO ${JENKINS_URL}jnlpJars/jenkins-cli.jar # or: https://cloudbees-ci.example.com/controller/jnlpJars/jenkins-cli.jar withCredentials([string(credentialsId: 'my-service-account', variable: 'ID_TOKEN')]) { sh 'java -jar jenkins-cli.jar -s https://cloudbees-ci.example.com/controller/ -auth bot:$ID_TOKEN some-command' }

In a Declarative Pipeline, bind the credentials in an environment block instead, which sets ID_TOKEN as an environment variable for use in your steps:

environment { ID_TOKEN = credentials('my-service-account') }

GitHub Actions example

The following example configures the operations center rbac.yaml to allow the GitHub Actions issuer domain and defines the service account:

removeStrategy: rbac: SYNC serviceAccountOidcIssuerDomains: - token.actions.githubusercontent.com serviceAccounts: - name: bot authentication: - type: oidc id: github-actions issuer: https://token.actions.githubusercontent.com subject: repo:my-org/my-repo:ref:refs/heads/main

You can then use it from a GitHub Actions workflow:

name: Operate on CloudBees CI on: workflow_dispatch: jobs: main: runs-on: ubuntu-latest permissions: id-token: write steps: - name: Contact some endpoint with an ID token run: | curl -u bot:$(curl -sSL -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://cloudbees-ci.example.com/controller/serviceAccounts/bot/" | jq -r .value) https://cloudbees-ci.example.com/controller/someEndpoint

Kubernetes example

The following example creates a Kubernetes ServiceAccount used by a Pod that mounts its token:

apiVersion: batch/v1 kind: Job metadata: name: use-bot spec: template: spec: restartPolicy: Never serviceAccountName: bot containers: - name: main image: ubuntu command: - sh - -c - | curl -u bot:$(cat /var/run/secrets/tokens/bot-token) https://cloudbees-ci.example.com/controller/someEndpoint volumeMounts: - name: bot-token mountPath: /var/run/secrets/tokens readOnly: true volumes: - name: bot-token projected: sources: - serviceAccountToken: path: bot-token expirationSeconds: 3600 audience: https://cloudbees-ci.example.com/controller/serviceAccounts/bot/

The following example configures the operations center rbac.yaml and defines the service account for a Google Kubernetes Engine (GKE) cluster:

removeStrategy: rbac: SYNC serviceAccountOidcIssuerDomains: - container.googleapis.com serviceAccounts: - name: bot authentication: - type: oidc id: kubernetes issuer: https://container.googleapis.com/v1/projects/your-project/locations/us-central1/clusters/your-cluster subject: system:serviceaccount:yournamespace:bot

For an Amazon Elastic Kubernetes Service (Amazon EKS) cluster, the issuer has the form:
https://oidc.eks.<region>.amazonaws.com/id/<cluster-id>

For example:
https://oidc.eks.us-east-1.amazonaws.com/id/ABCDEF1234567890ABCDEF1234567890

The corresponding entry in serviceAccountOidcIssuerDomains is:
amazonaws.com