= Handling Secrets :toc: :toc-placement: preamble :toclevels: 3 Almost every Jenkins instance defines credentials and other sensitive information, and JCasC offers ways to manage credentials and other sensitive information in the YAML configuration files. This page describes the available options. There are 3 ways to securely pass credentials in JCasC: * Using credential provider plugins * Passing secrets through variables * Passing secrets through encrypted strings == Using credential provider plugins link:https://plugins.jenkins.io/credentials[Credentials Plugin] is a standard way to manage credentials in Jenkins. This plugin offers the link:https://jenkins.io/doc/developer/extensions/credentials/#credentialsprovider[CredentialsProvider extension point] which might be used to use credentials from external sources. Examples of available plugins: * link:https://plugins.jenkins.io/kubernetes-credentials-provider[Kubernetes Credentials Provider] * link:https://plugins.jenkins.io/aws-secrets-manager-credentials-provider[AWS Secrets Manager Credentials Provider] * link:https://plugins.jenkins.io/azure-keyvault[Azure KeyVault] * link:https://go.cloudbees.com/docs/cloudbees-core/cloud-secure-guide/cyberark/#cyberark-credentials-provider[CyberARK Credential Provider] (proprietary plugin offered by CloudBees) When an external Credentials provider is configured in Jenkins through JCasC, there is no need to define credentials inside. Jenkins will be able to take the credentials from external storage by ID: ```yaml - kubernetes: connectTimeout: 5 containerCapStr: "10" credentialsId: "k8s-user-password" jenkinsUrl: "http://localhost:8080" ``` Using an external credentials source does **NOT** address all use-cases. There are configurations inside Jenkins (e.g. proxy password) which use a low-level link:https://javadoc.jenkins-ci.org/hudson/util/Secret.html[hudson.util.Secret] engine. For this type of credentials other engines should be used, see below. == Passing secrets through variables Currently, you can provide initial secrets to JCasC that all rely on substitution of strings in the configuration. For example, `Jenkins: "${some_var}"`. Default variable substitution using the `:-` operator from `bash` is also available. For example, `key: "${VALUE:-defaultvalue}"` will evaluate to `defaultvalue` if `$VALUE` is unset. To escape a string from secret interpolation, put `^` in front of the value. For example, `Jenkins: "^${some_var}"` will produce the literal `Jenkins: "${some_var}"`. === Additional variable substitution If you need to read a secret from a file, or perform additional en/decoding of the secret prior to use, the following helpers are available: - `base64` - `readFile` - `decodeBase64` - `readFileBase64` - `json` Helpers can be combined to do multiple transformations. Examples: - `${base64:${readFile:/secret/file.txt}}`: read the file -> base64-encode it - `${base64:${readFile:${SECRET_FILE_PATH}}}`: expand the SECRET_FILE_PATH variable -> read the file -> base64-encode it ==== base64 Encodes the provided secret to base64. Example: `${base64:HELLO WORLD}` -> `SEVMTE8gV09STEQ=` ==== decodeBase64 Decodes the provided base64-encoded secret into a plain text string. Example: ``` ${decodeBase64:${ENV_VAR}} ``` `decodeBase64` (and its related `readFileBase64`) may be useful for the credential plugin or any other plugin that needs binary base64 encoding of a file. This might help when handling link:https://tools.ietf.org/html/rfc7292[a PKCS#12 certificate files] or passing an SSH key as an environment variable. ==== file Alias for `readFile`. Example: ``` ${file:/secret/file.txt} ``` ==== fileBase64 Alias for `readFileBase64`. Example: ``` ${fileBase64:/secret/certificate.p12} ``` ==== readFile Reads the secret from the specified file. Example: ``` ${readFile:/secret/file.txt} ``` ==== readFileBase64 Read the specified binary file into a base64 representation. ``` ${readFileBase64:/secret/certificate.p12} ``` ==== json Parse the string secret as JSON, then extract the value for the specified ``. (An empty string is returned if the JSON secret does not contain ``). ``` ${json::${ENV_VAR}} ``` This can help in situations where the backend has the option to store JSON secrets, e.g. AWS Secrets Manager. Example: ``` ${json:username:${ENV_VAR}} ``` === Security and compatibility considerations // TODO(oleg_nenashev): Add a link to the advisory once ready Jenkins configurations might include property definitions, e.g. for Token Macro resolution in Mail Ext Plugin. Such properties are not supposed to be resolved when importing configurations, but the JCasC plugin has no way to determine which variables should be resolved when reading the configurations. In some cases non-admin users can contribute to JCasC exports if they have some permissions (e.g. agent/view configuration or credentials management), and they could potentially inject variable expressions in plain text fields like descriptions and then see the resolved secrets in Jenkins Web UI if the Jenkins admin exports and imports the configuration without checking contents. It led to a security vulnerability which was addressed in JCasC `1.25` (SECURITY-1446). - When reading configuration YAMLs, JCasC plugin will try to resolve **all** variables having the `${VARNAME}` format. - Starting from JCasC `1.25`, JCasC export escapes the internal variable expressions, e.g. as `^${VARNAME}`, so newly exported and then imported configurations are are not subject for this risk - For previously exported configurations, Jenkins admins are expected to manually resolve the issues by putting the escape symbol `^` in front of variables which should not be resolved === Secret sources In JCasC there is a link:https://jenkins.io/doc/developer/extensions/configuration-as-code/#secretsource[SecretSource extension point] which allows resolving variables passed to JCasC. We can provide these initial secrets in the following ways: - link:https://github.com/jenkinsci/aws-secrets-manager-secret-source-plugin[AWS Secrets Manager] - link:https://github.com/jenkinsci/configuration-as-code-secret-ssm-plugin[AWS Systems Manager Parameter Store] - link:https://github.com/jenkinsci/azure-keyvault-plugin#secretsource[Azure KeyVault] - Docker Secrets - Environment variables - link:https://github.com/jenkinsci/hashicorp-vault-plugin[HashiCorp Vault] - Kubernetes secrets ==== Docker secrets Files on path `/run/secrets/${KEY}` will be replaced by `${KEY}` in the configuration. The base folder `/run/secrets` can be overridden by setting the environment variable `SECRETS`. So this can be used as a file based secret, and not just docker secrets. ==== Kubernetes secrets Logic is the same as for docker-secrets. The secret needs to be mounted as a file to `/run/secrets/`, and then the filename can be used as the KEY. For example: ```yaml apiVersion: v1 kind: Secret metadata: name: secret-name data: filename: {{ "encoded string" | b64enc }} ``` can be used from a pod containing ```yaml containers: - name: jenkins volumeMounts: - name: filename-secret mountPath: /run/secrets/filename subPath: filename readOnly: true volumes: - name: filename-secret secret: secretName: secret-name ``` as: ```yaml - credentials: - string: id: "cred-id" secret: ${filename} ``` ==== HashiCorp Vault Secret Source Prerequisites: link:https://plugins.jenkins.io/hashicorp-vault-plugin[HashiCorp Vault plugin] v2.4.0+. - The environment variable `CASC_VAULT_PW` must be present, if token is not used and appRole/Secret is not used. (Vault password.) - The environment variable `CASC_VAULT_USER` must be present, if token is not used and appRole/Secret is not used. (Vault username.) - The environment variable `CASC_VAULT_APPROLE` must be present, if token is not used and U/P not used. (Vault AppRole ID.) - The environment variable `CASC_VAULT_APPROLE_SECRET` must be present, it token is not used and U/P not used. (Vault AppRole Secret ID.) - The environment variable `CASC_VAULT_TOKEN` must be present, if U/P is not used. (Vault token.) - The environment variable `CASC_VAULT_PATHS` must be present. (Comma separated vault key paths. For example, `secret/jenkins,secret/admin`.) - The environment variable `CASC_VAULT_URL` must be present. (Vault url, including port number.) - The environment variable `CASC_VAULT_MOUNT` is optional. (Vault auth mount. For example, `ldap` or another username & password authentication type, defaults to `userpass`.) - The environment variable `CASC_VAULT_NAMESPACE` is optional. If used, sets the Vault namespace for Enterprise Vaults. - The environment variable `CASC_VAULT_FILE` is optional, provides a way for the other variables to be read from a file instead of environment variables. - The environment variable `CASC_VAULT_ENGINE_VERSION` is optional. If unset, your vault path is assumed to be using kv version 2. If your vault path uses engine version 1, set this variable to `1`. - The issued token should have read access to vault path `auth/token/lookup-self` in order to determine its expiration time. JCasC will re-issue a token if its expiration is reached (except for `CASC_VAULT_TOKEN`). If the environment variables `CASC_VAULT_URL` and `CASC_VAULT_PATHS` are present, JCasC will try to gather initial secrets from Vault. However for it to work properly there is a need for authentication by either the combination of `CASC_VAULT_USER` and `CASC_VAULT_PW`, a `CASC_VAULT_TOKEN`, or the combination of `CASC_VAULT_APPROLE` and `CASC_VAULT_APPROLE_SECRET`. The authenticated user must have at least read access. You can also provide a `CASC_VAULT_FILE` environment variable where you load the secrets from a file. File should be in a Java Properties format ```properties CASC_VAULT_PW=PASSWORD CASC_VAULT_USER=USER CASC_VAULT_TOKEN=TOKEN CASC_VAULT_PATHS=secret/jenkins/master,secret/admin CASC_VAULT_URL=https://vault.dot.com CASC_VAULT_MOUNT=ldap ``` A good use for `CASC_VAULT_FILE` would be together with docker secrets. ```yaml version: "3.6" services: jenkins: environment: CASC_VAULT_FILE: /run/secrets/jcasc_vault restart: always build: . image: jenkins.master:v1.0 ports: - 8080:8080 - 50000:50000 volumes: - jenkins-home:/var/jenkins_home secrets: - jcasc_vault volumes: jenkins-home: secrets: jcasc_vault: file: ./secrets/jcasc_vault ``` ==== Using environment variables (not recommended for secrets) Although it is supported, it is not recommended to inject secrets using environment variables. Doing so is *unsafe* because the environment variables can be read by Jenkins admins and jobs running on the Jenkins controller. You can use this to abstract away non secret configuration values across environments. ==== Using properties file JCasC will try to resolve secrets via link:https://en.wikipedia.org/wiki/.properties[.properties] file if `/run/secrets/secrets.properties` exists. To change this default file path you can use the environment variable `SECRETS_FILE`. This file must be secured through machine ownership and permissions. == Passing credentials as encrypted text This is an additional engine which uses the link:https://javadoc.jenkins-ci.org/hudson/util/Secret.html[hudson.util.Secret] engine to define encrypted credentials in JCasC configuration files. * Encrypted credentials can be stored in plain text * Encryption is done using the Jenkins-internal secret key which is unique for every Jenkins instance. It means that the credentials are not portable between instances. * Encrypted credential values can be exported using the link:./configExport.md[configuration export] feature. NOTE: There is an open feature request for supporting portable credentials. See link:https://github.com/jenkinsci/configuration-as-code-plugin/issues/1141[JCasC #1141]. Configuration example: ```yaml credentials: system: domainCredentials: - credentials: - usernamePassword: id: "exampleuser-creds-id" username: "exampleuser" password: "{AQAAABAAAAAQ1/JHKggxIlBcuVqegoa2AdyVaNvjWIFk430/vI4jEBM=}" description: "Sample credentials of exampleuser" scope: GLOBAL ``` == Useful links * link:https://jenkins.io/doc/developer/security/secrets/[Jenkins Developer Guide: Storing Secrets in Jenkins]