Credenciales para GitHub Actions¶
Cómo consigue un workflow de GitHub Actions una credencial de kubelatch sin guardar ningún secreto en el repositorio.
Cómo funciona¶
GitHub Actions puede emitir un id_token OIDC firmado por GitHub, con el repositorio, la rama, el run_id y quien lanzó el job dentro. La action picaportelabs/kubelatch-action se lo pide a GitHub, se lo manda a kubelatch, y kubelatch lo canjea por una credencial de un bot, ya con su kubeconfig. No hay ningún secreto que guardar en GitHub ni que rotar.
La credencial dura 1 hora por defecto, suficiente para un job; un administrador puede cambiarlo con KUBELATCH_CI_TOKEN_TTL. Un job que dure más vuelve a usar la action.
sequenceDiagram
participant W as Workflow
participant G as GitHub
participant K as kubelatch
W->>G: la action pide un id_token (audiencia = url)
G-->>W: id_token firmado
W->>K: POST /v1/ci/github-actions/token {token}
K->>K: verifica el token y busca la regla de confianza
K-->>W: credencial del bot + kubeconfig (una vez)
W->>K: kubectl a través del proxy
Antes de empezar¶
Un administrador tiene que haber preparado esto antes (CI: reglas de confianza):
- Un bot dado de alta, con al menos un permiso concedido en el cluster que necesitas.
- Una regla de confianza que haga coincidir tu repositorio (por sus ids numéricos, no por nombre) con ese bot, opcionalmente restringida a una rama (
ref) o a unenvironment. - La URL de tu kubelatch, que el runner alcance por https, con un certificado en el que confíe (un kubelatch en un portátil o detrás de una red privada no sirve con los runners de GitHub).
El workflow¶
-
Crea
.github/workflows/kubelatch.ymlen tu repositorio con este contenido. Es el ejemplo que trae el repositorio de kubelatch, sin los comentarios de cabecera. Un administrador también puede pasarte el «Fragmento del workflow», que ya lleva la URL de tu kubelatch: lo tiene rellenado para tu regla justo al crearla («Conecta el workflow»), o en «Workflow de ejemplo» de la pantalla «CI».name: kubectl via kubelatch on: push: branches: [main] workflow_dispatch: permissions: id-token: write # required: lets the job request the OIDC token contents: read jobs: pods: runs-on: ubuntu-latest # environment: prod # uncomment if the trust rule sets an environment steps: - uses: picaportelabs/kubelatch-action@v1 with: url: https://kubelatch.example.com # exactly KUBELATCH_BASE_URL: it is the token's audience - name: kubectl through kubelatch run: | kubectl auth whoami # bot:<name> with the kubelatch:* groups of its grants kubectl get pods -n default -
Cambia
urlpor la URL de tu kubelatch. Tiene que ser elKUBELATCH_BASE_URLdel servidor, con el mismo esquema, host, puerto y ruta: es la audiencia que kubelatch exige en el token. La barra final da igual. - Si la regla de confianza fija un
environment, quita el#de la líneaenvironment:y pon su nombre. Sin él, elid_tokenno lleva el claim que la regla de confianza espera y el canje responde403. - Ajusta el último paso a lo que necesites hacer en el cluster. Todos los pasos siguientes del job usan la credencial:
kubectl,helmy cualquier herramienta que leaKUBECONFIG.
Qué hace la action¶
permissions:id-token: writees obligatorio. Sin él, el job no puede pedir elid_token.- Pide a GitHub un
id_tokenconurlcomo audiencia y lo enmascara en el log. Esa petición a GitHub falla de vez en cuando, así que la action la intenta hasta tres veces. - Canjea el token en
POST /v1/ci/github-actions/tokenuna sola vez: kubelatch nunca acepta dos veces el mismo token. Un201trae la credencial y su kubeconfig, una sola vez. - Enmascara la credencial antes de imprimir nada más, escribe el kubeconfig en un directorio propio bajo
$RUNNER_TEMP, que en runners de Linux y macOS solo puede leer el usuario del job (nunca en~/.kube/configdel runner), y apuntaKUBECONFIGa él.
Sus salidas son kubeconfig (la ruta del fichero), expires-at y clusters (los ids de los clusters a los que llega la credencial, como array JSON). Dale un id al paso, como id: kubelatch, para leerlas como ${{ steps.kubelatch.outputs.expires-at }}.
La action no tiene dependencias: dos ficheros cortos de JavaScript, src/main.js y src/action.js, que puedes leer en su repositorio antes de fiarte de ella. Si la política de tu organización lo pide, fíjala por commit (picaportelabs/kubelatch-action@<sha>) en lugar de @v1.
En un runner autogestionado, la action confía en el almacén de certificados del sistema, donde suele estar una CA privada, igual que curl, y además en el fichero de NODE_EXTRA_CA_CERTS si el job lo define. Pasa por HTTPS_PROXY (kubelatch y GitHub van los dos por https) salvo que NO_PROXY deje fuera a kubelatch.
Comprueba que funciona¶
Lanza el workflow (workflow_dispatch o un push a la rama configurada) y confirma:
- El job imprime
bot:<nombre-del-bot>enkubectl auth whoami. - La credencial aparece en «Credenciales» (para un administrador) con el nombre
github-actions, marcada, por ejemplo, «token de CI · run 1234 · octocat»; su detalle trae la notaowner/repo@refs/heads/main · run <id> · <actor>. - Las peticiones quedan en la auditoría con ese bot como sujeto.
Relanzar el job pide un id_token nuevo (cada token tiene su propio jti, así que un token capturado no sirve dos veces).
Si algo falla¶
El error de la action dice la causa y enlaza aquí.
this job cannot ask GitHub for an OIDC token: al workflow o al job les faltapermissions: id-token: write.GitHub answered the OIDC token request withun código: GitHub no pudo emitir el token ni al tercer intento. Relanza el job; si sigue fallando, mira el estado de GitHub.url must be https: kubelatch responde con una credencial, así que la action no acepta http sin cifrar fuera de la propia máquina del runner. Sirve kubelatch por https (Instalar).401 invalid token: elid_tokenno pasa la verificación (firma, emisor, audiencia distinta deKUBELATCH_BASE_URL, caducado, o ya canjeado antes). Revisa queurlsea exactamente elKUBELATCH_BASE_URLdel servidor; sin la action, revisa también que no estés reutilizando un token de un job anterior.403: no trust rule matches this workflow …: el mensaje trae losrepository_owner_id,repository_id,refyenvironmentdel token. Pide a un administrador que cree o ajuste la regla de confianza con esos valores, o que quiteref/environmentpara hacerla genérica.403nombrando al bot: el bot está deshabilitado o sin permisos activos en ningún cluster.404, oanswered 201 without a credential:urlno es la dirección de tu kubelatch.kubelatch answered 429: llegaron más de 60 canjes por minuto desde la dirección del runner. Relanza el job pasado un minuto.could not reach kubelatch(sin la action,curl: (7)o(35)): el runner no llega a kubelatch por https, o no confía en su certificado. Con una CA privada, añádela al almacén del sistema del runner o apuntaNODE_EXTRA_CA_CERTSa ella en elenvdel job; si no, es un problema de despliegue, no del workflow: pide a un administrador que revise la guía Instalar.
El listado completo de errores está en Errores.
Sin la action¶
Si tu organización solo permite sus propias actions, estos dos pasos en bash hacen lo mismo con curl y jq, que traen los runners alojados de GitHub. KUBELATCH_URL es la URL de tu kubelatch, igual letra por letra que el KUBELATCH_BASE_URL del servidor.
name: kubectl via kubelatch
on:
push:
branches: [main]
workflow_dispatch:
permissions:
id-token: write # required: lets the job request the OIDC token
contents: read
env:
KUBELATCH_URL: https://kubelatch.example.com # exactly KUBELATCH_BASE_URL: it is the token's audience
jobs:
pods:
runs-on: ubuntu-latest
# environment: prod # uncomment if the trust rule sets an environment
steps:
- name: Request the OIDC token for kubelatch
run: |
ID_TOKEN=$(curl -sSf -G \
-H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
--data-urlencode "audience=$KUBELATCH_URL" \
"$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value)
echo "::add-mask::$ID_TOKEN"
echo "ID_TOKEN=$ID_TOKEN" >> "$GITHUB_ENV"
- name: Exchange it for a kubelatch credential
run: |
RESPONSE=$(curl -sS -w '\n%{http_code}' -H 'Content-Type: application/json' \
-d "$(jq -n --arg token "$ID_TOKEN" '{token:$token}')" "$KUBELATCH_URL/v1/ci/github-actions/token")
STATUS=$(printf '%s' "$RESPONSE" | tail -n1)
BODY=$(printf '%s' "$RESPONSE" | sed '$d')
if [ "$STATUS" != "201" ]; then
echo "kubelatch answered $STATUS: $(printf '%s' "$BODY" | jq -r '.error // .')" >&2
exit 1
fi
# The token and kubeconfig are shown exactly once: keep them out of the log.
printf '%s' "$BODY" | jq -r .token | xargs -I{} echo "::add-mask::{}"
# Only this job's user may read it; never ~/.kube/config.
(umask 077 && printf '%s' "$BODY" | jq -r .kubeconfig > "$RUNNER_TEMP/kubeconfig")
chmod 600 "$RUNNER_TEMP/kubeconfig"
echo "KUBECONFIG=$RUNNER_TEMP/kubeconfig" >> "$GITHUB_ENV"
echo "credential expires at $(printf '%s' "$BODY" | jq -r .expires_at)"
- name: kubectl through kubelatch
run: |
kubectl auth whoami # bot:<name> with the kubelatch:* groups of its grants
kubectl get pods -n default
- Request the OIDC token: pide a GitHub un
id_tokencon la audienciaKUBELATCH_URLy lo enmascara en el log. - Exchange it: lo canjea en
POST /v1/ci/github-actions/token. Un201traecredential,token,kubeconfig,clustersyexpires_at, una sola vez. El paso enmascara el token y guarda el kubeconfig en$RUNNER_TEMP, nunca en~/.kube/configdel runner.