Skip to content

Proxy nginx compartido ​

Todos los dominios públicos del proyecto se sirven a través de un proxy inverso nginx compartido con otros consultores de la EJE (Debian, nginx + certbot). El mismo proxy atiende test y producción a la vez: termina TLS y reenvía cada dominio al puerto publicado del servicio en el host del entorno correspondiente.

Proxy compartido

El proxy aloja sitios de otros consultores. El despliegue automatizado solo puede tocar los dominios propios: cada dominio debe estar en la lista permitida del servidor y los archivos ajenos (sin marcador EJE-MANAGED) nunca se sobrescriben.

Convenciones del proxy ​

  • Un archivo por dominio en /etc/nginx/sites-available/<dominio> (sin extensión), con enlace simbólico en /etc/nginx/sites-enabled/.

  • Un certificado por dominio, emitido con certbot certonly --nginx -d <dominio>. El timer de certbot del servidor renueva todos los certificados.

  • Los archivos gestionados por el pipeline empiezan con el marcador:

    # EJE-MANAGED: infra-deploy/proxy - <dominio> ...

    Ese marcador identifica qué archivos puede sobrescribir el despliegue. Los archivos sin marcador se consideran de otros consultores y son intocables.

Configuración versionada (infra-deploy/proxy/) ​

La configuración ya no se edita a mano en el servidor: se versiona en el repositorio infra-deploy y se renderiza por entorno.

infra-deploy/proxy/
├── sites/
│   ├── test.txt              # dominios de test  → puerto en DEV_DEPLOY_HOST
│   └── prod.txt              # dominios de prod  → puerto en PROD_DEPLOY_HOST
├── templates/
│   ├── site-https.conf.template  # fase definitiva (443 + redirección 80)
│   └── site-http.conf.template   # fase 1: dominio aún sin certificado
├── scripts/
│   ├── render.sh             # renderiza un entorno (sustituye dominio/upstream/puerto)
│   └── validate.sh           # validación en CI: lint + nginx -t con certificados falsos
└── server/
    └── eje-proxy-deploy      # script root del servidor (instalación única manual)
  • sites/<entorno>.txt declara una línea <dominio> <puerto> por sitio. La rama develop publica los sitios de test.txt con upstream DEV_DEPLOY_HOST; la rama main, los de prod.txt con PROD_DEPLOY_HOST.
  • Las plantillas replican la convención del proxy (bloque 443 con certificados de Let's Encrypt + bloque 80 con redirección, client_max_body_size 30m, cabeceras X-Forwarded-*).

Dos fases por dominio nuevo ​

Un dominio nuevo no tiene certificado todavía, y nginx no puede cargar un bloque ssl sin certificado. Por eso cada dominio se renderiza en dos fases y eje-proxy-deploy elige automáticamente según exista /etc/letsencrypt/live/<dominio>/fullchain.pem:

  1. Fase HTTP (site-http): primer despliegue; sirve el sitio por el puerto 80. El job avisa que falta el certificado.
  2. Emisión manual del certificado en el proxy (único paso manual por dominio): certbot certonly --nginx -d <dominio>.
  3. Fase HTTPS (site-https): al volver a ejecutar el despliegue, detecta el certificado e instala la configuración definitiva.

El script eje-proxy-deploy ​

Fuente: infra-deploy/proxy/server/eje-proxy-deploy. Se instala una sola vez a mano como root en /usr/local/sbin/eje-proxy-deploy (ver runbook). El usuario de despliegue (deploy-eje, sin sudo general) solo puede ejecutar ese script vía sudo.

En cada ejecución el script:

  1. Valida el directorio recibido: manifest de dominios, marcador EJE-MANAGED en cada archivo, lista blanca de directivas nginx (proxy_pass solo hacia http://<host>:<puerto>, include solo del archivo de opciones de certbot, certificados solo bajo /etc/letsencrypt/live/<dominio>/, etc.).
  2. Autoriza: todos los dominios deben estar en /etc/eje-proxy/allowed-domains.txt (root-only, en el servidor).
  3. Protege: si sites-available/<dominio> existe sin marcador EJE-MANAGED, aborta sin tocarlo.
  4. Respalda los archivos que va a modificar en /var/backups/eje-proxy/<timestamp>/ (conserva los últimos 10 respaldos).
  5. Aplica solo los archivos que cambiaron (idempotente: sin cambios no se recarga nginx) y crea los enlaces en sites-enabled si faltan.
  6. Verifica con nginx -t; si falla, revierte todos los cambios desde el respaldo y termina con error.
  7. Recarga nginx (systemctl reload nginx) solo si hubo cambios.

El modo --lint ejecuta solo el paso 1 sin tocar el sistema; es el que usa el CI para validar con exactamente las mismas reglas que el servidor.

Jobs del pipeline ​

Ver también Pipelines CI/CD.

JobStageCuándo corre
validate-proxyvalidateTodo pipeline de rama o MR, salvo trigger con SERVICE_NAME=<servicio>
deploy-proxydeployTodo pipeline de develop/main (push, web o trigger), salvo SERVICE_NAME=<servicio>
  • validate-proxy renderiza test y prod, ejecuta eje-proxy-deploy --lint y corre nginx -t con certificados falsos para ambas fases. No toca servidores.
  • deploy-proxy renderiza el entorno de la rama, vuelve a hacer lint, sube la configuración por scp y ejecuta sudo /usr/local/sbin/eje-proxy-deploy <dir> en el proxy.
  • Antes de aplicar, el job compara el SHA-256 del script instalado en el servidor con el del repositorio y muestra un aviso si difieren (no falla; el script se reinstala a mano según el runbook).
  • SERVICE_NAME=<servicio> despliega solo ese servicio (el proxy no corre); SERVICE_NAME=proxy despliega solo el proxy (el orquestador no corre); un trigger sin SERVICE_NAME despliega orquestador y proxy.

Runbook: instalación única en el proxy ​

Pasos que se ejecutan una sola vez. Salvo que se indique lo contrario, los comandos se ejecutan como root en el proxy compartido.

  1. Generar el par de claves SSH (en su máquina local, no en el proxy):

    sh
    ssh-keygen -t ed25519 -N "" -C "deploy-eje@infra-deploy" -f ./deploy-eje_ed25519

    Genera dos archivos:

    • deploy-eje_ed25519 (privada): se carga en GitLab como PROXY_SSH_PRIVATE_KEY (File, Protected). No la guarde en ningún repositorio; elimine la copia local al terminar.
    • deploy-eje_ed25519.pub (pública): se pega en el authorized_keys del paso siguiente.
  2. Crear el usuario dedicado (sin sudo general):

    sh
    adduser --disabled-password --gecos "Deploy EJE" deploy-eje
    install -d -o deploy-eje -g deploy-eje -m 700 /home/deploy-eje/.ssh
    # Pegar el contenido de deploy-eje_ed25519.pub, restringido (sin forwarding ni pty):
    echo 'restrict ssh-ed25519 AAAA... deploy-eje@infra-deploy' \
    > /home/deploy-eje/.ssh/authorized_keys
    chown deploy-eje:deploy-eje /home/deploy-eje/.ssh/authorized_keys
    chmod 600 /home/deploy-eje/.ssh/authorized_keys

    No use command= fijo en la clave: el job necesita ejecutar mkdir, scp, sha256sum y el comando sudo del despliegue.

  3. Instalar el script root. El script se copia desde la estación del administrador y se verifica su SHA-256 antes de instalarlo.

    a. En su máquina local, desde el clon de infra-deploy en la rama que se va a desplegar (normalmente main), calcule el hash y copie el script a su propio usuario administrador en el proxy (no a deploy-eje, que no debe poder modificar el archivo antes de instalarlo):

    sh
    git -C infra-deploy pull
    sha256sum infra-deploy/proxy/server/eje-proxy-deploy
    scp infra-deploy/proxy/server/eje-proxy-deploy <admin>@<proxy>:eje-proxy-deploy.nuevo

    b. En el proxy, como root: copie el archivo a un directorio temporal propiedad de root, verifique el hash contra el valor del paso anterior e instálelo desde esa copia verificada:

    sh
    EXPECTED=<sha256 obtenido en el paso a>
    TMP=$(mktemp -d)                      # directorio 700 propiedad de root
    cp /home/<admin>/eje-proxy-deploy.nuevo "$TMP/eje-proxy-deploy"
    echo "$EXPECTED  $TMP/eje-proxy-deploy" | sha256sum -c - \
      && install -o root -g root -m 755 "$TMP/eje-proxy-deploy" /usr/local/sbin/eje-proxy-deploy
    rm -rf "$TMP" /home/<admin>/eje-proxy-deploy.nuevo
    sha256sum /usr/local/sbin/eje-proxy-deploy

    Si sha256sum -c no muestra OK, el archivo no se instala: vuelva a copiarlo.

    Hash de referencia

    El job deploy-proxy imprime el hash del script del repositorio (repo: <sha256>) cuando difiere del instalado. Sirve como segunda referencia, independiente de su máquina local, para el valor de EXPECTED.

    Sin acceso por scp

    Si solo dispone de una consola en el proxy, ejecute como root EXPECTED=... y TMP=$(mktemp -d), cree el archivo pegando el contenido en un editor (nano "$TMP/eje-proxy-deploy") y continúe con el paso b desde la línea sha256sum -c (omitiendo el cp). El hash detecta cualquier error de copiado o pegado.

  4. Autorizar solo ese comando por sudo:

    sh
    cat > /etc/sudoers.d/deploy-eje <<'EOF'
    deploy-eje ALL=(root) NOPASSWD: /usr/local/sbin/eje-proxy-deploy *
    EOF
    chmod 440 /etc/sudoers.d/deploy-eje
    visudo -c
  5. Crear la lista de dominios permitidos (root-only; es la frontera que impide que el pipeline toque dominios ajenos):

    sh
    install -d -m 755 /etc/eje-proxy
    cat > /etc/eje-proxy/allowed-domains.txt <<'EOF'
    backend.idp-test.eje.gob.bo
    auth.idp-test.eje.gob.bo
    admin.idp-test.eje.gob.bo
    cuenta.idp-test.eje.gob.bo
    backend.sps-test.eje.gob.bo
    admin.sps-test.eje.gob.bo
    comisiones.sps-test.eje.gob.bo
    postulantes.sps-test.eje.gob.bo
    estacion.sps-test.eje.gob.bo
    tecnico.docs-test.eje.gob.bo
    usuario.docs-test.eje.gob.bo
    backend.idp.eje.gob.bo
    auth.idp.eje.gob.bo
    admin.idp.eje.gob.bo
    cuenta.idp.eje.gob.bo
    backend.sps.eje.gob.bo
    admin.sps.eje.gob.bo
    comisiones.sps.eje.gob.bo
    postulantes.sps.eje.gob.bo
    estacion.sps.eje.gob.bo
    tecnico.docs.eje.gob.bo
    usuario.docs.eje.gob.bo
    EOF
    chmod 644 /etc/eje-proxy/allowed-domains.txt
  6. Configurar las variables CI/CD en GitLab (infra-deploy), ver tabla de variables.

  7. Adoptar sitios existentes (si un dominio ya estaba configurado a mano): el primer despliegue fallará con "existe y NO tiene el marcador EJE-MANAGED". Verifique que el archivo actual no tenga personalizaciones necesarias, respáldelo y elimínelo (o agréguele como primera línea el marcador # EJE-MANAGED: infra-deploy/proxy); el siguiente despliegue lo regenerará desde el repositorio.

Cuando el aviso de SHA-256 del pipeline indique que el script difiere del repositorio, repita únicamente el paso 3.

Cómo añadir un dominio ​

  1. Publicar el servicio en un puerto del host del entorno (docker-compose.yml en infra-deploy).
  2. Agregar la línea <dominio> <puerto> en proxy/sites/test.txt y/o proxy/sites/prod.txt.
  3. En el proxy, como root: agregar el dominio a /etc/eje-proxy/allowed-domains.txt (y crear el registro DNS si no existe).
  4. Hacer push a develop (o main). El pipeline valida y despliega la fase HTTP del dominio nuevo; el job avisa que falta el certificado.
  5. En el proxy, como root: certbot certonly --nginx -d <dominio>.
  6. Volver a ejecutar el despliegue del proxy (re-run del job deploy-proxy, o trigger con SERVICE_NAME=proxy). Se instala la fase HTTPS definitiva.
  7. Actualizar el mapa de subdominios y puertos del manual.

Rollback ​

  • Automático: si nginx -t falla tras aplicar, eje-proxy-deploy restaura los archivos desde el respaldo, vuelve a recargar nginx y el job falla.

  • Manual (volver a una configuración anterior): los respaldos quedan en /var/backups/eje-proxy/<timestamp>/ (últimos 10). Como root:

    sh
    ls /var/backups/eje-proxy/
    cp /var/backups/eje-proxy/<timestamp>/<dominio> /etc/nginx/sites-available/<dominio>
    nginx -t && systemctl reload nginx
  • Vía Git (preferido): revertir el commit en infra-deploy y dejar que el pipeline aplique la configuración anterior (es idempotente).

Solución de problemas ​

SíntomaCausa probable / acción
dominio no autorizado en este servidorFalta el dominio en /etc/eje-proxy/allowed-domains.txt (paso root en el proxy)
existe y NO tiene el marcador EJE-MANAGEDEl dominio ya tenía un archivo manual: adoptarlo (runbook, paso 7)
directiva no permitida / proxy_pass inválidoLa plantilla o el render generaron algo fuera de la lista blanca; corregir en proxy/templates/
Aviso aún no tiene certificado; se instala la fase HTTPNormal en dominios nuevos: emitir el certificado y redesplegar (fases 2 y 3)
nginx -t falló; los cambios fueron revertidosRevisar el log del job; el servidor quedó con la configuración anterior
Aviso de SHA-256 distintoEl script instalado quedó desactualizado: reinstalar (runbook, paso 3)
sudo: a password is required en el jobFalta o está mal /etc/sudoers.d/deploy-eje
Error loading key "(stdin)": error in libcryptoLa clave no llegó como clave OpenSSH válida. El job acepta PROXY_SSH_PRIVATE_KEY como File o Variable; verifique que contenga la clave privada completa (-----BEGIN OPENSSH PRIVATE KEY----- … END), sin passphrase
El certificado no renuevaLa renovación es del timer de certbot del servidor (compartido); revisar systemctl list-timers y certbot renew --dry-run como root

Variables CI/CD del proxy ​

Se configuran en infra-deploy → Settings → CI/CD → Variables. No llevan prefijo de entorno porque un solo proxy sirve test y producción.

VariableTipoProtected / MaskedEjemplo
PROXY_DEPLOY_HOSTVariableProtected172.17.1.10
PROXY_DEPLOY_USERVariableProtecteddeploy-eje
PROXY_SSH_PRIVATE_KEYFileProtectedContenido de la clave privada ed25519

Además, deploy-proxy reutiliza DEV_DEPLOY_HOST y PROD_DEPLOY_HOST (ya configuradas para el orquestador) como upstream de los sitios de test y producción respectivamente.