Módulo 2: Anatomy Of A Github Actions Workflow

7. Manos a la obra: pasando secretos a `act`

Descripción

Un workflow real casi nunca corre sin al menos una credencial —una clave de API, un token, en esta guía las credenciales dummy de LocalStack—. GitHub Actions resuelve esto con Secrets, gestionados fuera del YAML (Módulo 4 los cubre a fondo). Esta lección te enseña el lado de act: cómo pasarle esos mismos secretos a una corrida local, sin escribirlos jamás dentro del archivo de workflow ni dejarlos entrar a un commit. Vas a crear .secrets —gitignorado desde el momento exacto en que existe— y vas a confirmar, con salida literal, que un secreto llega a un step y que su ausencia también se nota.

Conexión con el módulo

Esta lección sigue trabajando dentro de andes-cargo-infra/, construyendo directamente sobre lo que dejaste en la lección 6. El proyecto de este módulo (lección 8) usa exactamente el mecanismo de esta lección para pasarle a hello-andes-cargo.yml las credenciales dummy que necesita para hablar con LocalStack. El Módulo 4 vuelve sobre secretos con mucho más detalle —GitHub Secrets reales, scoping por ambiente, y por qué una credencial de vida larga en un repositorio es el antipatrón de seguridad más citado de la competencia—, pero el mecanismo de act que aprendes aquí no cambia.


Analogía: la caja fuerte con combinación separada del plano

Si pr-event.json (lección 6) era el guion de un ensayo, un secreto es la combinación de una caja fuerte que aparece en la utilería de esa obra. Nunca escribirías la combinación real en el guion impreso —que cualquiera del elenco, del staff, de futuras producciones va a leer—; se la das al actor por separado, en el momento, fuera del texto que queda archivado para siempre. .secrets es exactamente eso: un archivo que vive fuera del historial de Git —nunca se commitea—, que le entrega la combinación a act en el momento de la corrida, sin que esa combinación quede grabada en ningún lado permanente.


Paso 1 — Crear .secrets, y gitignorarlo desde ahora

Sigue parado en andes-cargo-infra/. .secrets, en la raíz del proyecto:

AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=test

Las credenciales dummy test/test son las mismas que LocalStack acepta sin validar contra ninguna cuenta real de AWS —las mismas que ya usaste en terraform-and-iac-guide y aws-core-services-guide—. Antes de hacer nada más, protege este archivo:

echo "" >> .gitignore
echo "# Local secrets for \`act --secret-file\` (never commit real credentials)" >> .gitignore
echo ".secrets" >> .gitignore

Confirma que Git ya lo ignora:

git status --short

Qué esperar (literal).secrets no debería aparecer en ningún lado de la salida, ni siquiera como archivo sin seguimiento; solo .gitignore (modificado) debería figurar:

 M .gitignore

Si .secrets apareciera en esta lista, algo está mal en tu .gitignore — revísalo antes de seguir. Este es, literalmente, el momento exacto que el diseño de esta guía marca como "gitignorado desde esta lección en adelante": nunca hubo un commit con .secrets adentro, ni siquiera uno viejo que después se corrigió.


Paso 2 — Un workflow que confirma los secretos, sin imprimir su valor

.github/workflows/secrets-test.yml:

name: secrets-test

on: workflow_dispatch

jobs:
  check-secrets:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    steps:
      - name: Confirm the secrets arrived, without printing their value
        run: |
          if [ -n "$AWS_ACCESS_KEY_ID" ]; then
            echo "AWS_ACCESS_KEY_ID is set (length: ${#AWS_ACCESS_KEY_ID})"
          else
            echo "AWS_ACCESS_KEY_ID is EMPTY"
          fi
          if [ -n "$AWS_SECRET_ACCESS_KEY" ]; then
            echo "AWS_SECRET_ACCESS_KEY is set (length: ${#AWS_SECRET_ACCESS_KEY})"
          else
            echo "AWS_SECRET_ACCESS_KEY is EMPTY"
          fi

secrets.AWS_ACCESS_KEY_ID es la sintaxis de contexto que lee un Secret con ese nombre —de la misma familia que github.event... de la lección 6, pero para el contexto secrets, específicamente reservado para este tipo de valor—. Fíjate que el step nunca imprime el valor real del secreto, solo su longitud — una práctica deliberada: incluso con credenciales dummy como estas, vale la pena acostumbrarte a nunca volcar un secreto completo a un log, ni siquiera en un laboratorio.


Paso 3 — act --secret-file .secrets

act workflow_dispatch -j check-secrets --secret-file .secrets

Qué esperar (salida literal, ejecutada para escribir esta lección):

[secrets-test/check-secrets] ⭐ Run Set up job
[secrets-test/check-secrets] 🚀  Start image=catthehacker/ubuntu:act-latest
[secrets-test/check-secrets]   ✅  Success - Set up job
[secrets-test/check-secrets] ⭐ Run Main Confirm the secrets arrived, without printing their value
[secrets-test/check-secrets]   | AWS_ACCESS_KEY_ID is set (length: 4)
[secrets-test/check-secrets]   | AWS_SECRET_ACCESS_KEY is set (length: 4)
[secrets-test/check-secrets]   ✅  Success - Main Confirm the secrets arrived, without printing their value [60.883584ms]
[secrets-test/check-secrets] 🏁  Job succeeded

Longitud 4 — el número de caracteres de la palabra test, sin que el valor mismo aparezca en ningún lado de la salida. Confirma que el secreto llegó, sin comprometerlo.

Un hallazgo real: .secrets es el nombre por defecto de act

Antes de seguir, vale la pena una verificación honesta. Corrí el mismo comando sin el flag --secret-file .secrets:

act workflow_dispatch -j check-secrets

Qué esperar (salida literal, ejecutada para escribir esta lección — el resultado sorprende):

[secrets-test/check-secrets]   | AWS_ACCESS_KEY_ID is set (length: 4)
[secrets-test/check-secrets]   | AWS_SECRET_ACCESS_KEY is set (length: 4)

Los secretos llegaron igual, sin el flag. Verificado contra act --help (versión 0.2.89, la de esta guía): --secret-file string file with list of secrets to read from (e.g. --secret-file .secrets) (default ".secrets"). act ya busca, por defecto, un archivo llamado exactamente .secrets en el directorio de trabajo actual — el mismo patrón que .actrc, que tampoco necesita que lo invoques explícitamente.

Esto no cambia lo que esta lección te enseña: seguir escribiendo --secret-file .secrets de forma explícita —como hiciste arriba— es una buena práctica, no un paso innecesario. Un pipeline reproducible no debería depender de que quien lo corre sepa, de memoria, una convención de nombre implícita de act — ser explícito documenta la intención directamente en el comando, y sigue funcionando sin cambios si algún día decides nombrar el archivo distinto (por ejemplo, .secrets.dev y .secrets.prod, un patrón real que vas a ver mencionado en el Módulo 4).


Paso 4 — La alternativa -s CLAVE=valor, para un secreto suelto

A veces no vale la pena crear un archivo completo para un único valor —por ejemplo, mientras pruebas algo puntual—. act acepta secretos sueltos directamente en la línea de comandos con -s. Un segundo workflow, deliberadamente simple, para practicar esto de forma aislada. .github/workflows/single-secret-test.yml:

name: single-secret-test

on: workflow_dispatch

jobs:
  check-one-secret:
    runs-on: ubuntu-latest
    env:
      DUMMY_TOKEN: ${{ secrets.DUMMY_TOKEN }}
    steps:
      - name: Confirm a secret passed with -s arrived
        run: |
          if [ -n "$DUMMY_TOKEN" ]; then
            echo "DUMMY_TOKEN is set (length: ${#DUMMY_TOKEN})"
          else
            echo "DUMMY_TOKEN is EMPTY"
          fi
act workflow_dispatch -j check-one-secret -s DUMMY_TOKEN=demo-value

Qué esperar (salida literal, ejecutada para escribir esta lección):

[single-secret-test/check-one-secret] ⭐ Run Set up job
[single-secret-test/check-one-secret] 🚀  Start image=catthehacker/ubuntu:act-latest
[single-secret-test/check-one-secret]   ✅  Success - Set up job
[single-secret-test/check-one-secret] ⭐ Run Main Confirm a secret passed with -s arrived
[single-secret-test/check-one-secret]   | DUMMY_TOKEN is set (length: 10)
[single-secret-test/check-one-secret]   ✅  Success - Main Confirm a secret passed with -s arrived [64.558333ms]
[single-secret-test/check-one-secret] 🏁  Job succeeded

Longitud 10 — el largo exacto de demo-value. -s es útil para un valor puntual, temporal, que no necesita vivir en un archivo; --secret-file es la forma correcta cuando necesitas varios secretos consistentes entre corridas —como las credenciales de AWS que va a necesitar el proyecto de la lección 8—.

Commitea lo que sí debe quedar en el historial —los workflows, nunca .secrets—:

git add -A
git commit -m "Add .secrets (gitignored) and secrets-test workflows for act --secret-file / -s"

Errores comunes

Commitear .secrets antes de gitignorarlo (el error más caro de esta lección). Qué pasa: alguien crea .secrets con credenciales reales —no las dummy test/test de LocalStack— y hace git add -A antes de actualizar .gitignore. Por qué pasa: el orden natural es "primero creo el archivo que necesito, después me acuerdo de protegerlo" — exactamente al revés de lo que hizo esta lección. Cómo detectarlo: git status --short muestra .secrets en la lista de archivos, o peor, git log -p muestra que ya entró a un commit. Cómo corregirlo: si todavía no hiciste commit, arregla el .gitignore y usa git rm --cached .secrets para sacarlo del área de staging sin borrar el archivo local. Si ya hiciste commit con una credencial real adentro —no una dummy—, el problema es más serio de lo que un simple .gitignore resuelve: esa credencial queda en el historial de Git para siempre, recuperable por cualquiera con acceso al repositorio, incluso después de borrar el archivo en un commit posterior. La única solución real en ese caso es revocar la credencial expuesta inmediatamente —tema que retoma el Módulo 4, lección 2, con el caso completo.

Confundir el env: del job con el nombre del Secret (de sintaxis). Qué pasa: alguien escribe env: { AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY }} }, con nombres distintos a cada lado, y después el .secrets define una línea AWS_ACCESS_KEY_ID=test —el nombre de la variable de entorno, no el nombre del Secret que espera el YAML—. Cómo detectarlo: el secreto sale vacío en el step, sin ningún error explícito. Cómo corregirlo: el nombre a la izquierda de = en .secrets tiene que coincidir exactamente con el nombre que aparece dentro de secrets.<NOMBRE> en el YAML —en esta lección, AWS_ACCESS_KEY_ID en ambos lados—; el nombre de la variable de entorno del lado izquierdo del env: del job puede ser distinto, si quisieras, pero mantenerlos iguales (como hace esta lección) evita justamente este tipo de error.


Ejercicios

Ejercicio 1 — Explica por qué la longitud, no el valor. Un colega te pregunta por qué el step de esta lección imprime length: 4 en vez de simplemente echo "$AWS_ACCESS_KEY_ID", ya que de todas formas es una credencial dummy de LocalStack sin ningún riesgo real. Respóndele en dos frases.

Ver solución

Una respuesta completa suena, más o menos, así: "Es cierto que test/test no representa ningún riesgo real aquí — pero el hábito de nunca volcar un secreto completo a un log es lo que evita el error el día que sí importa, con una credencial real. Practicar la disciplina de verificar 'llegó, tiene el largo esperado' en vez de 'imprimo el valor para confirmar' es exactamente el tipo de reflejo que previene una fuga accidental en un pipeline de producción."

Ejercicio 2 — Predice el comportamiento sin el archivo. Si borraras .secrets por completo y corrieras act workflow_dispatch -j check-secrets sin ningún flag de secreto, ¿qué esperas ver en la salida, según lo que aprendiste en esta lección?

Ver solución

Esperarías ver AWS_ACCESS_KEY_ID is EMPTY y AWS_SECRET_ACCESS_KEY is EMPTY — sin .secrets en el directorio (ni el flag --secret-file apuntando a otro archivo), act no tiene ningún valor que asignarle a secrets.AWS_ACCESS_KEY_ID dentro del YAML, así que esa expresión se resuelve como una cadena vacía. El job no falla —GitHub Actions no valida automáticamente que un secreto exista, a menos que el propio workflow lo verifique, como hace el if [ -n "$AWS_ACCESS_KEY_ID" ] de esta lección.

Ejercicio 3 — Elige entre --secret-file y -s para tres escenarios. Para cada situación, indica cuál usarías: (a) las credenciales dummy de LocalStack que vas a necesitar en cada corrida del resto de esta guía; (b) un token de prueba que necesitas una sola vez, para confirmar que un step lee bien secrets.X; (c) preparar un pipeline real que en el futuro va a usar GitHub Secrets de verdad, y quieres que el comando local se parezca lo más posible a como correría en CI real.

Ver solución

(a) --secret-file .secrets — necesitas consistencia entre corridas repetidas, y varios valores a la vez; un archivo es la forma correcta. (b) -s CLAVE=valor — es exactamente el caso de uso de la línea de comandos: rápido, temporal, sin dejar ningún archivo nuevo en el proyecto. (c) --secret-file .secrets — en un pipeline real de GitHub, los Secrets vienen todos juntos, gestionados centralmente (Módulo 4); un archivo .secrets con varias líneas se parece mucho más a ese modelo que pasar cada uno por separado con -s.


Resumen y siguiente paso

En esta lección creaste .secrets —gitignorado desde antes de escribir una sola línea de credencial adentro—, y confirmaste, con salida literal, que act --secret-file .secrets entrega esos valores a un workflow sin exponerlos en ningún log. También descubriste, verificado contra act --help, que .secrets es el nombre de archivo por defecto que act busca aunque no pases el flag explícitamente — y por qué seguir pasándolo de forma explícita sigue siendo la práctica correcta. Cerraste con -s CLAVE=valor, la alternativa para un secreto suelto.

Antes de avanzar deberías poder: crear un .secrets correctamente gitignorado desde el primer momento; explicar la diferencia entre --secret-file y -s, y cuándo usar cada uno; y decir de memoria por qué act encuentra .secrets incluso sin el flag explícito.

Tienes las dos técnicas de simulación completas: eventos (lección 6) y secretos (esta lección). La lección 8 —el proyecto de este módulo— las junta en hello-andes-cargo.yml, el primer workflow que de verdad intenta alcanzar el LocalStack de tu host desde dentro del contenedor del job.

Recursos

  1. nektosact.com — User Guide — documentación oficial de --secret-file y -s, incluido el valor por defecto de .secrets.
  2. GitHub Docs — Using secrets in GitHub Actions — el modelo real de Secrets de GitHub, retomado a fondo en el Módulo 4.
  3. terraform-and-iac-guide, Módulo 1 (NIEVA) — el origen de las credenciales dummy test/test de LocalStack, heredadas sin cambios en esta lección.