Using self-signed certificates in CloudBees CI on Kubernetes

5 minute read

CloudBees CI includes an option called sidecar injector. This option lets you use a self-signed certificate or a custom certificate authority (CA) to access internal HTTPS services, such as an SCM repository or an artifact repository.

Sidecar injector is designed only to trust services that are secured with custom or self-signed certificates. It is not intended to be used to secure a CloudBees CI cluster using HTTPS and should not be set up for that purpose.

Using sidecar injector provides the following benefits:

  • Satisfies the enterprise security policies for organizations that use custom certificate authorities.

  • Removes the cost of purchasing commercially signed certificates for internal websites.

  • Saves time because organizations don’t have to wait for signed certificates.

To use sidecar injector, create a certificate bundle that overwrites the default certificate bundle. Then, set up sidecar injector to inject that certificate bundle into all containers of all scheduled Kubernetes pods in a labeled namespace.

Prerequisites for using self-signed certificates on Kubernetes

The following items are required:

  • A currently supported version of Kubernetes, with admission controller MutatingAdmissionWebhook enabled.

    In order to check whether it is enabled for your cluster, start running the following command:

    kubectl api-versions | grep admissionregistration.k8s.io/v1beta1

    The result should be:

    admissionregistration.k8s.io/v1beta1

    This means the APIs are available on your cluster.

  • In addition, the MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controllers should be added and listed in the correct order in the enable-admission-plugins flag of kube-apiserver. The way to check this depends on the Kubernetes distribution.

    For public cloud offerings, such as Amazon EKS, AKS or GKE, they are enabled. For other distributions, check the corresponding documentation or ask the provider to determine whether it is available.

Network requirements

The sidecar injector listens to HTTPS requests on port 443, and the firewall rules of that port must be configured accordingly:

FromToPortDescription

Kubernetes Control Plane

Kubernetes Node(s)

443

Allow Kubernetes control plane to communicate with sidecar-injector pod(s)

Kubernetes Node(s)

Kubernetes Control Plane

443

Allow incoming requests from sidecar-injector pod(s)

In environments where aggregator routing is enabled, the API Server routes the webhook requests directly to the Sidecar Injector Endpoint that listens on port 8443, rather than the Kubernetes Service that listens on port 443. In such environments, the firewall rules must allow the Kubernetes control plane to communicate with Kubernetes node(s) on the port 8443.

Installing self-signed certificates on Kubernetes

This procedure requires a context with cluster-admin privilege to create the MutatingWebhookConfiguration.

Sidecar Injector is delivered as a Helm chart. It can be installed either directly using Helm or as a yaml manifest produced by helm template.

You may have previously used a different method for setting up self-signed certificates. If so, please refer to our article detailing that process to undo those changes before continuing here.

Creating a certificate bundle

These instructions assume you are working in the namespace where CloudBees CI is installed, and that the certificate you want to install is named mycertificate.pem.

If you are using a self-signed certificate, add the certificate itself.

If the certificate has been issued from a custom root CA, add the root CA itself.

  1. Copy reference files locally:

    On CloudBees CI 2.204.1.3 or newer:

    kubectl cp cjoc-0:etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem ./ca-certificates.crt
    kubectl cp cjoc-0:etc/pki/ca-trust/extracted/java/cacerts ./cacerts

    On earlier versions, or when overriding the Docker image selection to use the Alpine base:

    kubectl cp cjoc-0:etc/ssl/certs/ca-certificates.crt ./ca-certificates.crt
    kubectl cp cjoc-0:etc/ssl/certs/java/cacerts ./cacerts

    Alternatively, in case you do not have an operations center pod running, you can retrieve the files from the image without deploying in Kubernetes.

    For this you will need to have a working docker installation.

    You can then run:

    CBCI_VERSION="2.249.2.4"
    CONTAINER_ID=$(docker create cloudbees/cloudbees-cloud-core-oc:${CBCI_VERSION})
    # adapt for each file you need to retrieve
    docker cp ${CONTAINER_ID}:etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem .
    docker rm ${CONTAINER_ID}
  2. Add root CA to system certificate bundle:

    cat mycertificate.pem >> ca-certificates.crt
  3. Add root CA to java cacerts:

    keytool -import -noprompt -keystore cacerts -file mycertificate.pem -storepass changeit -alias service-mycertificate;
    Make sure that mycertificate.pem contains only one certificate. keytool does not support importing multiple certificates from a single file.
  4. Create a configmap with the two files above in your namespace (change mynamespace in this command to be the namespace you would like to use sidecar injector with):

    kubectl create configmap --from-file=ca-certificates.crt,cacerts ca-bundles -n mynamespace

Setting up the sidecar injector on Kubernetes using Helm

  1. Create a namespace to deploy the sidecar injector.

    kubectl create namespace cloudbees-sidecar-injector
  2. Install sidecar-injector using one of the following:

    The Helm command:

    helm repo update
    helm install cloudbees-sidecar-injector cloudbees/cloudbees-sidecar-injector --namespace cloudbees-sidecar-injector

    The Helm template command:

    helm template cloudbees-sidecar-injector cloudbees/cloudbees-sidecar-injector --namespace cloudbees-sidecar-injector | kubectl apply -n cloudbees-sidecar-injector -f -
  1. Verify everything is running.

    The cloudbees-sidecar-injector pod should be running

    kubectl --namespace cloudbees-sidecar-injector get pods

    The output should be similar to:

    NAME                                                  READY     STATUS    RESTARTS   AGE
    cloudbees-sidecar-injector-bbb689d69-882dd   1/1       Running   0          5m

    The deployment should have one pod that is running and up to date:

    kubectl --namespace cloudbees-sidecar-injector get deployment

    The output should be similar to:

    NAME                                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    cloudbees-sidecar-injector   1         1         1            1           5m

Configuring a namespace for sidecar injector on Kubernetes

  1. Label the namespace where CloudBees CI is installed with sidecar-injector=enabled.

    In the command below, replace "mynamespace" with the name of your namespace. For example, if your namespace is "cb", use the command kubectl label namespace cb sidecar-injector=enabled. This causes pods that start in that namespace to have the sidecar injected.

    If you configure sidecar injector to use its own namespace, make sure that the namespace does not contain the sidecar-injector=enabled label.

    kubectl label namespace mynamespace sidecar-injector=enabled
  2. Check the following:

    kubectl get namespace -L sidecar-injector

    The output should be similar to:

    NAME                       STATUS    AGE       SIDECAR-INJECTOR
    cloudbees-sidecar-injector Active    1h
    default                    Active    18h
    kube-public                Active    18h
    kube-system                Active    18h
    mynamespace                Active    18h       enabled

Verifying the namespace for sidecar injector on Kubernetes

  1. Deploy an app in Kubernetes cluster, take sleep app as an example.

    kubectl run sleep -n mynamespace --generator=run-pod/v1 --image tutum/curl --serviceaccount default --command /bin/sleep infinity
  2. Verify injection has happened.

    kubectl get pods -n mynamespace -o 'go-template={{range .items}}{{.metadata.name}}{{"\n"}}{{range $key,$value := .metadata.annotations}}* {{$key}}: {{$value}}{{"\n"}}{{end}}{{"\n"}}{{end}}'

    The output should be similar to:

    sleep
    * com.cloudbees.sidecar-injector/status: injected
  3. Delete the sleep pod.

    kubectl delete pod sleep -n mynamespace

Applying the certificate bundle

After you have completed the setup for sidecar injector, you can use your custom CA across your cluster.

To apply the new certificate bundle:

  1. Use CJOC_RUL/restart to restart operations center and the pod.

  2. Restart any running managed controllers.

When new build agents are scheduled, the certificate bundle is automatically applied and permits connection to remote endpoints using your certificates.

Advanced configuration for sidecar injector on Kubernetes

Disabling Injection on a specific pod

To disable implicit injection for a specific pod, annotate it with com.cloudbees.sidecar-injector/inject: no

Making injection explicit

By default, injection is implicit and applies to all pods created in the labeled namespace(s). However, you can alternately enable injection on only the pod(s) that explicitly require it.

To make injection explicit for a given pod:

  1. Edit the sidecar-injector-webhook-configmap configmap and specify requiresExplicitInjection: true.

  2. To enable injection on a specific pod, annotate the pod with com.cloudbees.sidecar-injector/inject: yes.

Troubleshooting self-signed certificates on Kubernetes

The sleep pod can’t be created

There is an error on pod creation, such as "certificate signed by unknown authority", or the sidecar-injector logs contain the following: http: TLS handshake error from aaa.bbb.ccc.ddd:nnnnn: remote error: tls: bad certificate.

This can happen if the API server TLS certificate differs from the cluster signing certificate. To fix this, you need to provide the cluster signing certificate as an input to the installation.

Create a values.yaml file as follows. Replace the content with your own certificate.

caBundleCrt: |-
  -----BEGIN CERTIFICATE-----
  MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
  cm5ldGVzMB4XDTE5MDcyOTA3MzQyMloXDTI5MDcyNjA3MzQyMlowFTETMBEGA1UE
  AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALgQ
  fjIm8iVEpmX3tlcUzuH2BAQGzK0utC6S0hhqnK6zlQ9iGDAwDpAiwGB9VzcJSfK7
  fr3rx4zT9rWfVeot+ARQV/NPxSUpPlGK8WsRleg5wKyUdnE1xKZDly2l2Vpqlr0J
  GnMXb2A0roi685XZo6iQALLfo+rtWQ2y2JLXzGYYCB1sAUX3hM3qbYmIMReBIyMX
  YGUUdaMuWU1YazKy3eJ84Am7l9ZXlMm7infJlAFsM3BCKed9ZxO2KxTvhWv1qbUk
  Bj3GJrL2bJfQi3B6h0piiBDt6YeI3U8yU4EyxtMKQwQXs9T1zHloc6RmVGYYVAEl
  HquW/XIk4ebWTxYND6UCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
  /wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABsJvnyo14a8FR6y4JGSX1SXSmtS
  uRiWH6qo5Ou+4zIw0LIhDQTtaN44G86BzhvQFdeQzfvyrKXfuYUTOQz/iYPNFsO6
  FOcDg9EcA19n5tSGp8SniyDEe6EhBWa5A9UR2RPEg/8NZRoeZ/2G9SjUqoa/Erxn
  IPlZvNu+gMEK7etysUQ33s4fp+jD6p0pbWKgSQAiDRVHi3Khlhcn7DfM0ncrSQBs
  vgFPSEczjpl8LR6c0pLSdPUHgdK6pDLTYdtdytRNfJAVjAREYL4uSb8I5NKdkgEz
  BLvnDfdDoCeOZoaeLR68jCltNfdzT5/d1v086i7uRepGhQ5w7ehtPuZ+0U8=
  -----END CERTIFICATE-----

Then, run the installation again.

helm delete cloudbees-sidecar-injector
helm install cloudbees-sidecar-injector cloudbees/cloudbees-sidecar-injector --namespace cloudbees-sidecar-injector --values values.yaml

The sleep pod is created but stays in ContainerCreating state

Describe the pod:

kubectl describe po sleep

You may get an error like the following:

Warning  FailedMount  2s (x3 over 3s)  kubelet, docker-desktop  MountVolume.SetUp failed for volume "bundles" : configmap "ca-bundles" not found

If so, please verify that you created the certificate bundle mentioned above. Also check that you are working in the expected namespace. A configmap called ca-bundles must be created in each namespace you wish to use sidecar injector with.