← Volver al blog

Instalar los certificados Russian Trusted CA en contenedores Docker

Publicado el
4 min de lectura
--- vistas

Una integración de pagos con la API de un banco ruso empezó de repente a fallar desde dentro de un contenedor Docker. Cada llamada a https://securepay.tinkoff.ru/v2/Init moría con la misma línea:

cURL error 60: SSL certificate problem: self signed certificate in certificate chain

El código de la aplicación estaba bien. El almacén de confianza dentro del contenedor no lo estaba. Si ejecutas cualquier integración autoalojada con T-Bank, Sber u otro banco ruso desde un contenedor, esto también te va a pasar a ti — ya sea ahora, porque tu paquete de CA está desactualizado, o más adelante, cuando el banco pase al Russian Trusted CA (la raíz del Ministerio de Desarrollo Digital). Aquí tienes cómo diagnosticarlo correctamente y arreglarlo para que sobreviva a las reconstrucciones de la imagen.

Diagnostica antes de tocar nada

El cURL error 60 tiene dos causas completamente distintas, y necesitas saber cuál es la tuya. Reproduce el handshake desde dentro del contenedor exacto que hace la llamada saliente:

docker exec my-php-container \
  openssl s_client -connect securepay.tinkoff.ru:443 -servername securepay.tinkoff.ru 2>&1 \
  | grep -iE 'verify|return code'

Un verify return code: 19 (self signed certificate in certificate chain) significa que el contenedor no confía en la raíz que presenta el servidor. Ahora comprueba cuán antiguo es tu paquete:

docker exec my-php-container dpkg -l ca-certificates | tail -1

Si ves algo como 20210119, el paquete es anterior a raíces que hoy en día son perfectamente públicas. En el caso anterior, la cadena llevaba a HARICA TLS RSA Root CA 2021 — una CA pública legítima — de la que un paquete de 2021 sencillamente nunca tuvo constancia. Esa es la causa número uno: un paquete ca-certificates desactualizado.

La causa número dos es la migración que los bancos rusos anunciaron: cuando sus certificados actuales se revoquen, la API pasará al Russian Trusted CA emitido por el Ministerio de Desarrollo Digital. Esa raíz no está por defecto en ningún sistema operativo, runtime de lenguaje o librería HTTP, y nunca lo estará. Actualizar ca-certificates no hace nada por ella — hay que instalarla a mano.

Instala ambas raíces

Arregla el paquete desactualizado y añade las raíces rusas en un solo paso. Descarga los dos certificados una vez — la raíz y la intermedia:

curl -fsSL -o russian_trusted_root_ca.crt https://gu-st.ru/content/lending/russian_trusted_root_ca_pem.crt
curl -fsSL -o russian_trusted_sub_ca.crt  https://gu-st.ru/content/lending/russian_trusted_sub_ca_pem.crt

En un contenedor basado en Debian/Ubuntu, colócalos en el directorio local de anclaje y actualiza:

cp russian_trusted_*.crt /usr/local/share/ca-certificates/
apt-get update && apt-get install -y --only-upgrade ca-certificates
update-ca-certificates

El paso --only-upgrade ca-certificates es el que trae las raíces públicas más nuevas y arregla la causa uno. update-ca-certificates recoge las raíces rusas de /usr/local/share/ca-certificates/ y arregla la causa dos. En Alpine es el mismo directorio de anclaje pero un gestor de paquetes distinto:

apk add --no-cache ca-certificates && update-ca-certificates

Verifícalo de la manera correcta

No lo compruebes buscando un nombre con grep en el paquete — grep "Russian Trusted" ca-certificates.crt no devuelve nada incluso en una configuración correcta, porque el paquete almacena DER en base64 y etiquetas de archivo, no texto del sujeto. Ese falso negativo hace perder tiempo. Verifica por confianza en su lugar: una raíz autofirmada solo pasa openssl verify contra el paquete del sistema si realmente está instalada y es de confianza.

openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt \
  /usr/local/share/ca-certificates/russian_trusted_root_ca.crt
# russian_trusted_root_ca.crt: OK

Luego vuelve a ejecutar el handshake original y confirma verify return code: 0 (ok).

Haz que sobreviva a una reconstrucción

Ejecutar esos comandos dentro de un contenedor en marcha arregla el problema hoy y lo pierde todo en el momento en que ejecutas docker compose build. En su lugar, incorpora los certificados a la imagen. Añade los certificados a tu contexto de build y agrega esto al Dockerfile:

COPY certs/russian_trusted_root_ca.crt /usr/local/share/ca-certificates/russian_trusted_root_ca.crt
COPY certs/russian_trusted_sub_ca.crt  /usr/local/share/ca-certificates/russian_trusted_sub_ca.crt
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && update-ca-certificates && rm -rf /var/lib/apt/lists/*

Una advertencia: algunos runtimes ignoran el almacén de confianza del sistema operativo. Node.js y Python leen el suyo propio, así que configura también las variables de entorno en la imagen:

ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt

Haz esto en cada imagen que realice llamadas salientes — el contenedor web, el worker de colas y cualquier contenedor de CLI/cron — porque cada uno tiene su propio sistema de archivos y su propio almacén de confianza. Instala las raíces ahora, antes de que el banco active el cambio, y la migración se convertirá en un no-evento en lugar de una caída en producción.

Disponible para colaboración por contrato

Estoy disponible para colaborar por contrato. Si tiene una idea de proyecto interesante, reserve una llamada por Calendly.

Agenda una llamada de 30 min