Saltar a contenido

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. El workflow se lo pide a GitHub, se lo manda a kubelatch, y kubelatch lo cambia por una credencial de un bot, ya con su kubeconfig. No hay ningún secreto que guardar en GitHub ni que rotar.

sequenceDiagram
    participant W as Workflow
    participant G as GitHub
    participant K as kubelatch
    W->>G: pide un id_token (audience = KUBELATCH_URL)
    G-->>W: id_token firmado
    W->>K: POST /v1/ci/github-actions/token {token}
    K->>K: comprueba el token y busca la trust rule
    K-->>W: credencial + kubeconfig del bot (una vez)
    W->>K: kubectl a través del proxy

Antes de empezar

Un administrador tiene que haber preparado esto antes (CI: trust rules):

  • Un bot dado de alta, con al menos un permiso concedido en el cluster que necesitas.
  • Una trust rule que haga coincidir tu repositorio (por sus ids numéricos, no por nombre) con ese bot, opcionalmente restringida a una rama (ref) o a un environment.
  • Un KUBELATCH_URL 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).

El workflow

  1. Crea .github/workflows/kubelatch.yml en tu repositorio con este contenido. Es el ejemplo que trae el repositorio de kubelatch en examples/github-actions/kubelatch.yml, sin los comentarios. Un administrador también puede pasarte el «Fragmento del workflow» de la pantalla «CI», que ya lleva la URL de tu kubelatch.

    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
              printf '%s' "$BODY" | jq -r .token | xargs -I{} echo "::add-mask::{}"
              (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
    
  2. Cambia KUBELATCH_URL por la URL de tu kubelatch. Tiene que coincidir letra por letra con el KUBELATCH_BASE_URL del servidor: es la audiencia que kubelatch exige en el token.

  3. Si la trust rule fija un environment, quita el # de la línea environment: y pon su nombre. Sin él, el id_token no lleva el claim que la trust rule espera y el canje responde 403.
  4. Ajusta el último paso a lo que necesites hacer en el cluster.

Qué hace cada paso

  • permissions: id-token: write es obligatorio. Sin él, el job no puede pedir el id_token.
  • Request the OIDC token: pide a GitHub un id_token con la audiencia KUBELATCH_URL y lo enmascara en el log.
  • Exchange it: lo canjea en POST /v1/ci/github-actions/token. Un 201 trae credential, token, kubeconfig, clusters y expires_at, una sola vez. El paso enmascara el token y guarda el kubeconfig en $RUNNER_TEMP, nunca en ~/.kube/config del runner.
  • kubectl through kubelatch: a partir de aquí, kubectl usa ese kubeconfig a través del proxy.

Comprueba que funciona

Lanza el workflow (workflow_dispatch o un push a la rama configurada) y confirma:

  • El job imprime bot:<nombre-del-bot> en kubectl auth whoami.
  • La credencial aparece en «Credenciales» (para un administrador) con el nombre github-actions y una nota owner/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 ejecución tiene su propio jti, así que un token capturado no sirve dos veces).

Si algo falla

  • 401 token no válido: el id_token no pasa la verificación (firma, emisor, audiencia distinta de KUBELATCH_BASE_URL, caducado, o ya canjeado antes). Revisa que KUBELATCH_URL coincida exactamente con KUBELATCH_BASE_URL y que no estés reusando un token de un job anterior.
  • 403 ninguna trust rule coincide ...: el mensaje trae los repository_owner_id, repository_id, ref y environment del token. Pide a un administrador que cree o ajuste la trust rule con esos valores, o que quite ref/environment para hacerla genérica.
  • 403 nombrando al bot: el bot está deshabilitado o sin permisos activos en ningún cluster.
  • El runner no llega a kubelatch (curl: (7) o (35)): falta alcance de red por https, o el certificado no es de confianza. 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.