Mateus Müller

O carinha do Linux

06 ago. 2022

Jenkins no Kubernetes: unable to find valid certification path to requested target

Faaaaala meus queridos! Eis que eu ressurjo das cinzas para falar um pouco do meu novo projeto. Atualmente, estou montando um homelab com 2x Optiplex rodando Proxmox e um cluster Kubernetes (tenho documentado tudo no meu Instagram).

Neste processo, criei uma Certificate Authority privada utilizando o Vault dentro do cluster, usado para assinar todos os certificados das outras aplicações (Gitea, Jenkins, Grafana, etc). Ao tentar configurar o plugin do Gitea para receber webhooks, me deparei com o problema clássico: unable to find valid certification path to requested target.

O que significa o erro “unable to find valid certification path to requested target”?

Isso significa que o Jenkins está tentando se conectar com uma determinada URL, mas o TLS handshake está falhando, já que ele não confia na CA que assinou o certificado.

É comum esse cenário quando usamos uma CA privada, então o que precisamos fazer é importar esse certificado no Jenkins.

Como resolver este erro?

Primeiro é importante salientar que eu fiz o deploy do Jenkins no Kubernetes utilizando este Helm Chart. Portanto, meu foco era tentar encontrar uma solução apenas modificando os values.yaml do chart.

Depois de muitos testes, cheguei em mais ou menos o seguinte:

  1. Criar uma keystore do Java personalizando contendo o certificado da CA privada.
  2. Criar um secret no Kubernetes contendo esse arquivo.
  3. Adicionar esse secret no chart to Jenkins.
  4. Alterar os parâmetros de inicialização do Java dentro do chart para usar essa keystore.

Vamos lá então!

Criando uma keystore customizada

A primeira coisa é copiar a keystore atualmente usada pelo pod do Jenkins para a minha máquina pessoal, pra que então eu pudesse personalizar ela, e então modificar o resto.

$ k exec -it -n jenkins jenkins-0 sh

$ cd $JAVA_HOME/lib/security

$ ls -l
total 400
-rw-r--r-- 1 root root   2488 Jul 13 17:50 blocked.certs
-rw-r--r-- 1 root root 155878 Jul 13 17:50 cacerts
-rw-r--r-- 1 root root   9596 Jul 13 17:50 default.policy
-rw-r--r-- 1 root root 232578 Jul 13 17:50 public_suffix_list.dat

Você vai ver um arquivo chamado “cacerts” que é a keystore padrão usada pelo Java. Agora vamos sair do pod e copiar esse arquivo para nossa máquina pessoal no diretório tmp.

$ cd /tmp

$ k cp -n jenkins jenkins-0:/opt/java/openjdk/lib/security/cacerts keystore.jks

$ ls -lh keystore.jks 
-rw-rw-r-- 1 mateus mateus 153K Aug  7 14:44 keystore.jks

Você pode listar todos os certificados contidos nesse arquivo com o seguinte comando:

$ keytool -list -keystore keystore.jks

A senha padrão dessa keystore é changeit.

Acredito que você tenha o certificado da CA em um .pem ou .crt, então basta rodar o seguinte comando:

$ keytool -import \
  -trustcacerts \
  -alias <apelido> \
  -file <caminho>/certificado.crt \
  -keystore keystore.jks

O próximo passo é criar um secret contendo esse arquivo.

Criando secret e modificando o chart

Eu criei um secret contendo a senha e a keystore, mas ainda não consegui ver 100% como reutilizar a senha no chart.

$ k create secret generic -n jenkins jenkins-https-jks \
  --from-literal https-jks-password=changeit \
  --from-file jenkins-jks-file=keystore.jks

Se você der um describe no secret, vai ver duas keys, uma com a senha e outra com a keystore.

$ k describe secret -n jenkins jenkins-https-jks

Name:         jenkins-https-jks
Namespace:    jenkins
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
https-jks-password:  8 bytes
jenkins-jks-file:    156877 bytes

O próximo passo era montar o secret dentro do Jenkins. Olhando o values.yaml do chart, eu achei uma flag chamada additionalExistingSecrets que é basicamente para informar secrets já criados e que você quer montar dentro do pod.

Coloquei dessa forma então:

  ...
  additionalExistingSecrets:
   - name: jenkins-https-jks
     keyName: jenkins-jks-file
   - name: jenkins-https-jks
     keyName: https-jks-password
  ...

Depois de modificar o values, apenas fiz um upgrade do chart com tudo atualizado.

$ helm upgrade --install \
  --namespace jenkins \
  --create-namespace jenkins jenkins/jenkins \
  -f values.yaml

Os secrets são montados como arquivos dentro do /run/secrets/additional.

$ k exec -it -n jenkins jenkins-0 sh

$ ls -lh /run/secrets/additional
total 0
lrwxrwxrwx 1 root jenkins 27 Aug  6 18:29 chart-admin-password -> ..data/chart-admin-password
lrwxrwxrwx 1 root jenkins 27 Aug  6 18:29 chart-admin-username -> ..data/chart-admin-username
lrwxrwxrwx 1 root jenkins 43 Aug  6 18:29 jenkins-https-jks-https-jks-password -> ..data/jenkins-https-jks-https-jks-password
lrwxrwxrwx 1 root jenkins 41 Aug  6 18:29 jenkins-https-jks-jenkins-jks-file -> ..data/jenkins-https-jks-jenkins-jks-file

Você pode ainda confirmar se seu certificado está lá com o seguinte comando:

$ keytool -list -keystore jenkins-https-jks-jenkins-jks-file | grep -i mateus

Enter keystore password:  *********
mateusmullerme, Aug 4, 2022, trustedCertEntry,

Chegamos na reta final! Agora é só modificar as opções do Java para usar essa nova keystore.

Alterando a keystore padrão do Java

No mesmo values.yaml do chart, eu achei uma opção chamada javaOpts ao qual podemos passar parâmetros que serão inseridos na variável ambiente JAVA_OPTS.

Modifiquei para ficar dessa forma:

  ...
  javaOpts: >
      -Djavax.net.ssl.trustStore=/run/secrets/additional/jenkins-https-jks-jenkins-jks-file
      -Djavax.net.ssl.trustStorePassword=changeit
  ...

E aí mandei um novo upgrade:

$ helm upgrade --install \
  --namespace jenkins \
  --create-namespace jenkins jenkins/jenkins \
  -f values.yaml

E boom! Prontinho. Não tive mais problemas.

Se você der um describe no pod, dá pra ter uma ideia do que foi modificado.

$ k describe po -n jenkins jenkins-0

...
  Environment:
    SECRETS:                   /run/secrets/additional
    POD_NAME:                  jenkins-0 (v1:metadata.name)
    JAVA_OPTS:                 -Dcasc.reload.token=$(POD_NAME) -Djavax.net.ssl.trustStore=/run/secrets/additional/jenkins-https-jks-jenkins-jks-file -Djavax.net.ssl.trustStorePassword=changeit
    JENKINS_OPTS:              --webroot=/var/jenkins_cache/war 
    JENKINS_SLAVE_AGENT_PORT:  50000
    CASC_JENKINS_CONFIG:       /var/jenkins_home/casc_configs
...

É claro que não é o ideal informar esse secret direto no values.yaml. Talvez seria melhor criar uma variável ambiente com o valor do secret, e passar a variável ambiente nos parâmetros do Java, mas ainda não consegui parar pra refazer isso.

Enfim, espero que ajude! Perdi um tempinho nisso e decidi compartilhar a luta aqui rsrs. Grande abs!

Buy me a coffeeBuy me a coffee
Comentários Disqus