Compartir comentarios
Las respuestas se generan en base a la documentación.

Usar Docker Hardened Images con Red Hat OpenShift

Docker Hardened Images (DHI) se pueden desplegar en Red Hat OpenShift Container Platform, pero el modelo de seguridad de OpenShift difiere del estándar de Kubernetes de maneras que requieren una configuración específica. Debido a que OpenShift ejecuta los contenedores con un ID de usuario asignado arbitrariamente en lugar del ID por defecto de la imagen, debes ajustar la propiedad de los archivos y los permisos de grupo en tus Dockerfiles para garantizar que las rutas de escritura sigan siendo accesibles.

Esta guía explica cómo desplegar Docker Hardened Images en entornos OpenShift, cubriendo las restricciones de contexto de seguridad (Security Context Constraints o SCCs), la asignación de ID de usuario arbitrarios, los requisitos de permisos de archivos y las mejores prácticas tanto para las variantes de imágenes de desarrollo como de tiempo de ejecución.

Cómo difiere la seguridad de OpenShift de la de Kubernetes

OpenShift extiende Kubernetes con Security Context Constraints (SCCs), las cuales controlan qué acciones puede realizar un pod y a qué recursos puede acceder. Mientras que el Kubernetes estándar utiliza los Pod Security Standards (PSS) para propósitos similares, las SCCs son más granulares y se aplican por defecto.

Las diferencias clave que afectan los despliegues de DHI son:

ID de usuario arbitrarios. Por defecto, OpenShift ejecuta contenedores utilizando un ID de usuario (UID) asignado arbitrariamente de un rango asignado a cada proyecto. La SCC por defecto restricted-v2 (introducida en OpenShift 4.11) utiliza la estrategia MustRunAsRange, que anula la directiva USER en la imagen del contenedor con un UID del rango asignado al proyecto (que normalmente comienza por encima de 1000000000). Esto significa que aunque una imagen DHI especifique un usuario no root (UID 65532), OpenShift ejecutará el contenedor con un UID diferente e impredecible.

Requisito de grupo root. OpenShift asigna el UID arbitrario al grupo root (GID 0). El proceso del contenedor siempre se ejecuta con gid=0(root). Cualquier directorio o archivo en el que el proceso necesite escribir debe pertenecer al grupo root (GID 0) con permisos de lectura/escritura de grupo. Esto se encuentra documentado en las directrices de Red Hat para la creación de imágenes.

Important

Las imágenes DHI establecen la propiedad de los archivos en nonroot:nonroot (65532:65532) por defecto. Dado que el UID arbitrario de OpenShift NO está en el grupo nonroot (65532), no puede escribir en esos archivos, a pesar de que el pod sea admitido por la SCC y el contenedor se inicie. Debes cambiar la propiedad de grupo a GID 0 para cualquier ruta de escritura. Esta es la fuente más común de errores de permisos al desplegar DHI en OpenShift.

Restricciones de capacidades. La SCC restricted-v2 elimina todas las capacidades de Linux por defecto y aplica allowPrivilegeEscalation: false, runAsNonRoot: true y un seccompProfile de tipo RuntimeDefault. Las imágenes de tiempo de ejecución DHI ya cumplen con estas restricciones porque se ejecutan como un usuario no root y no requieren capacidades elevadas.

Traer imágenes DHI a OpenShift

Antes de realizar el despliegue, crea un secreto de descarga (pull secret) de imágenes para que tu clúster de OpenShift pueda autenticarse en el registro de DHI o en tu repositorio espejo en Docker Hub.

Crear un secreto de descarga de imágenes

oc create secret docker-registry dhi-pull-secret \
    --docker-server=docker.io \
    --docker-username=<your-docker-username> \
    --docker-password=<your-docker-access-token> \
    --docker-email=<your-email>

Si estás descargando directamente desde dhi.io en lugar de un repositorio espejo, establece --docker-server=dhi.io.

Vincular el secreto a una cuenta de servicio

Vincula el secreto de descarga a la cuenta de servicio (service account) default de tu proyecto para que todos los despliegues puedan descargar imágenes DHI automáticamente:

oc secrets link default dhi-pull-secret --for=pull

Para utilizar el secreto con una cuenta de servicio específica en su lugar:

oc secrets link <service-account-name> dhi-pull-secret --for=pull

Compilar imágenes compatibles con OpenShift a partir de DHI

Las imágenes de tiempo de ejecución DHI son distroless: no contienen shell, ni gestor de paquetes, ni un entorno compatible con RUN. Esto significa que no puedes utilizar comandos RUN en la etapa de tiempo de ejecución de tu Dockerfile. Todos los ajustes de permisos de archivos para OpenShift deben realizarse en la etapa de compilación -dev y los resultados deben copiarse en la etapa de tiempo de ejecución utilizando COPY --chown.

El patrón principal para la compatibilidad con OpenShift es:

  1. Utiliza una variante DHI -dev como etapa de compilación (tiene una shell).
  2. Compila tu aplicación y establece la propiedad de GID 0 en la etapa de compilación.
  3. Copia los resultados en la imagen de tiempo de ejecución DHI utilizando COPY --chown=<UID>:0.

Ejemplo: Nginx para OpenShift

# Etapa de compilación: tiene una shell, puede ejecutar comandos
FROM YOUR_ORG/dhi-nginx:1.29-alpine3.23-dev AS build

# Copiar configuración personalizada y establecer propiedad del grupo root
COPY nginx.conf /tmp/nginx.conf
COPY default.conf /tmp/default.conf

# Preparar directorios de escritura con GID 0
# (Nginx necesita escribir en la caché, los logs y las ubicaciones de los archivos PID)
RUN mkdir -p /tmp/nginx-cache /tmp/nginx-run && \
    chgrp -R 0 /tmp/nginx-cache /tmp/nginx-run && \
    chmod -R g=u /tmp/nginx-cache /tmp/nginx-run

# Etapa de tiempo de ejecución: distroless, SIN shell, SIN comandos RUN
FROM YOUR_ORG/dhi-nginx:1.29-alpine3.23

COPY --from=build --chown=65532:0 /tmp/nginx.conf /etc/nginx/nginx.conf
COPY --from=build --chown=65532:0 /tmp/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build --chown=65532:0 /tmp/nginx-cache /var/cache/nginx
COPY --from=build --chown=65532:0 /tmp/nginx-run /var/run
Important

Utiliza siempre --chown=<UID>:0 (usuario:grupo-root) al copiar archivos a la etapa de tiempo de ejecución. Esto garantiza que el UID arbitrario que OpenShift asigna pueda acceder a los archivos a través de la pertenencia al grupo root. Nunca utilices RUN en la etapa de tiempo de ejecución; las imágenes distroless DHI no tienen shell.

Note

El UID para las imágenes DHI varía según la imagen. La mayoría utiliza 65532 (nonroot), pero algunas (como la imagen de Node.js) pueden utilizar un UID diferente. Verifícalo con: docker inspect dhi.io/<image>:<tag> --format '{{.Config.User}}'

Desplegar en OpenShift:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-dhi
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-dhi
  template:
    metadata:
      labels:
        app: nginx-dhi
    spec:
      containers:
        - name: nginx
          image: YOUR_ORG/dhi-nginx:1.29-alpine3.23
          ports:
            - containerPort: 8080
          securityContext:
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
            capabilities:
              drop:
                - ALL
      imagePullSecrets:
        - name: dhi-pull-secret

DHI Nginx escucha en el puerto 8080 por defecto (no en el 80), lo cual es compatible con el requisito de no root. No se necesitan cambios en la SCC.

Ejemplo: Aplicación Node.js para OpenShift

# Etapa de compilación: la variante dev tiene shell y npm
FROM YOUR_ORG/dhi-node:24-alpine3.23-dev AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Establecer GID 0 en todo lo que el tiempo de ejecución necesita para escribir
RUN chgrp -R 0 /app/dist /app/node_modules && \
    chmod -R g=u /app/dist /app/node_modules

# Etapa de tiempo de ejecución: distroless, SIN shell
FROM YOUR_ORG/dhi-node:24-alpine3.23
WORKDIR /app
COPY --from=build --chown=65532:0 /app/dist ./dist
COPY --from=build --chown=65532:0 /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

Desplegar:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: node-app
  template:
    metadata:
      labels:
        app: node-app
    spec:
      containers:
        - name: app
          image: YOUR_ORG/dhi-node-app:latest
          ports:
            - containerPort: 3000
          securityContext:
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
            capabilities:
              drop:
                - ALL
      imagePullSecrets:
        - name: dhi-pull-secret

Gestionar ID de usuario arbitrarios

La SCC restricted-v2 de OpenShift asigna un UID aleatorio al proceso del contenedor. Este UID no existirá en /etc/passwd dentro de la imagen, pero el contenedor se ejecutará de todos modos; el proceso simplemente no tendrá un nombre de usuario asociado.

Esto puede causar problemas con aplicaciones que:

  • Buscan el directorio de inicio o el nombre de usuario del usuario actual
  • Escriben en directorios propiedad de un UID específico
  • Comprueban /etc/passwd para el usuario en ejecución

Añadir una entrada passwd para el UID arbitrario

Algunas aplicaciones (en particular aquellas que utilizan ciertas bibliotecas de Python o Java) requieren una entrada /etc/passwd válida para el usuario en ejecución. Puedes solucionar esto con un script de punto de entrada (entrypoint) que sirva de envoltura (wrapper).

Como este patrón requiere una shell, solo funciona con variantes DHI -dev o con una imagen personalizada DHI Enterprise que incluya una shell. Prepara la imagen en la etapa de compilación:

FROM YOUR_ORG/dhi-python:3.13-alpine3.23-dev AS build
# ... compilar tu aplicación ...

# Hacer que /etc/passwd sea escribible por el grupo para que el punto de entrada pueda añadir información
RUN chgrp 0 /etc/passwd && chmod g=u /etc/passwd

# Crear la envoltura (wrapper) del punto de entrada
RUN printf '#!/bin/sh\n\
if ! whoami > /dev/null 2>&1; then\n\
  if [ -w /etc/passwd ]; then\n\
    echo "${USER_NAME:-appuser}:x:$(id -u):0:dynamic user:/tmp:/sbin/nologin" >> /etc/passwd\n\
  fi\n\
fi\n\
exec "$@"\n' > /entrypoint.sh && chmod +x /entrypoint.sh

# Este patrón requiere una variante -dev en tiempo de ejecución (tiene shell)
FROM YOUR_ORG/dhi-python:3.13-alpine3.23-dev
COPY --from=build --chown=65532:0 /app ./app
COPY --from=build --chown=65532:0 /entrypoint.sh /entrypoint.sh
COPY --from=build --chown=65532:0 /etc/passwd /etc/passwd
USER 65532
ENTRYPOINT ["/entrypoint.sh"]
CMD ["python", "app/main.py"]
Note

Para imágenes de tiempo de ejecución distroless (sin shell), el patrón de inyección de passwd no es posible. En su lugar, utiliza la SCC nonroot (descrita en la siguiente sección) para ejecutar el proceso con el UID incorporado de la imagen para que la entrada existente en /etc/passwd coincida con el proceso en ejecución. Alternativamente, OpenShift 4.x inyecta automáticamente el UID arbitrario en /etc/passwd en la mayoría de los casos, lo que resuelve esto para muchas aplicaciones.

Usar la SCC nonroot para UIDs fijos

Si tu aplicación requiere ejecutarse con el UID específico definido en la imagen (normalmente 65532 para DHI), puedes utilizar la SCC nonroot en lugar de la restricted-v2 por defecto. La SCC nonroot utiliza la estrategia MustRunAsNonRoot, que permite cualquier UID que no sea cero.

Important

Para que funcione la SCC nonroot, la directiva USER de la imagen debe especificar un UID numérico (por ejemplo, 65532), no una cadena de nombre de usuario como nonroot. OpenShift no puede verificar que un nombre de usuario se asocie a un UID distinto de cero. Verifica tu imagen DHI con: docker inspect YOUR_ORG/dhi-node:24-alpine3.23 --format '{{.Config.User}}' Si la salida es una cadena de texto en lugar de un número, establece runAsUser explícitamente en la especificación del pod (pod spec).

Crea una cuenta de servicio y concédele la SCC nonroot:

oc create serviceaccount dhi-nonroot
oc adm policy add-scc-to-user nonroot -z dhi-nonroot

Haz referencia a la cuenta de servicio en tu despliegue:

spec:
  template:
    spec:
      serviceAccountName: dhi-nonroot
      containers:
        - name: app
          image: YOUR_ORG/dhi-node:24-alpine3.23
          securityContext:
            runAsUser: 65532
            runAsNonRoot: true
            allowPrivilegeEscalation: false
            seccompProfile:
              type: RuntimeDefault
            capabilities:
              drop:
                - ALL

Verifica la asignación de la SCC después del despliegue:

oc get pod <pod-name> -o jsonpath='{.metadata.annotations.openshift\.io/scc}'

Esto debería devolver nonroot.

Al utilizar la SCC nonroot con un UID fijo, el proceso se ejecuta como 65532 (coincidiendo con la propiedad de los archivos de la imagen), por lo que los ajustes de GID 0 no son estrictamente necesarios para rutas que ya pertenecen a 65532. Sin embargo, se sigue recomendando aplicar chown <UID>:0 para la portabilidad en ambas SCCs restricted-v2 y nonroot.

Usar variantes DHI dev en OpenShift

Las variantes DHI -dev incluyen una shell, un gestor de paquetes y herramientas de desarrollo. Se ejecutan como root (UID 0) por defecto, lo cual entra en conflicto con la SCC restricted-v2 de OpenShift. Existen tres enfoques:

Opción 1: Usar variantes dev únicamente en las etapas de compilación (recomendado)

Utiliza variantes -dev únicamente en las etapas de compilación del Dockerfile y nunca las despliegues directamente en OpenShift:

FROM YOUR_ORG/dhi-node:24-alpine3.23-dev AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Establecer la propiedad del grupo root para compatibilidad con OpenShift
RUN chgrp -R 0 /app/dist /app/node_modules && \
    chmod -R g=u /app/dist /app/node_modules

FROM YOUR_ORG/dhi-node:24-alpine3.23
WORKDIR /app
COPY --from=build --chown=65532:0 /app/dist ./dist
COPY --from=build --chown=65532:0 /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

La imagen final en tiempo de ejecución es no root y distroless, totalmente compatible con restricted-v2.

Opción 2: Conceder la SCC anyuid para depuración

Si necesitas ejecutar una variante -dev directamente en OpenShift para depuración, concede la SCC anyuid a una cuenta de servicio dedicada:

oc create serviceaccount dhi-debug
oc adm policy add-scc-to-user anyuid -z dhi-debug

Luego haz referencia a ella en tu pod:

apiVersion: v1
kind: Pod
metadata:
  name: dhi-debug
spec:
  serviceAccountName: dhi-debug
  containers:
    - name: debug
      image: YOUR_ORG/dhi-node:24-alpine3.23-dev
      command: ["sleep", "infinity"]
  imagePullSecrets:
    - name: dhi-pull-secret
Important

La SCC anyuid permite la ejecución con cualquier UID, incluido root. Utiliza esto únicamente para depuración temporal; nunca en cargas de trabajo de producción.

Opción 3: Usar oc debug o contenedores efímeros

Para imágenes de tiempo de ejecución distroless sin shell, utiliza herramientas de depuración nativas de OpenShift en lugar de docker debug (que solo funciona con Docker Engine, no con CRI-O en OpenShift).

Utiliza oc debug para crear una copia de un pod con una shell de depuración:

# Crear un pod de depuración basado en un despliegue
oc debug deployment/nginx-dhi

# Sobrescribir la imagen para usar una variante -dev con una shell
oc debug deployment/nginx-dhi --image=YOUR_ORG/dhi-node:24-alpine3.23-dev

Utiliza contenedores efímeros (OpenShift 4.12+ / Kubernetes 1.25+):

kubectl debug -it <pod-name> --image=YOUR_ORG/dhi-node:24-alpine3.23-dev \
    --target=app -- sh

Esto acopla un contenedor de depuración temporal a un pod en ejecución sin reiniciarlo, compartiendo el espacio de nombres de procesos del pod.

Note

docker debug es una característica de Docker Desktop/CLI para desarrollo local. No está disponible en clústeres de OpenShift, los cuales utilizan CRI-O como su entorno de ejecución de contenedores.

Desplegar charts de Helm de DHI en OpenShift

DHI proporciona charts de Helm preconfigurados para aplicaciones populares. Al desplegar estos charts en OpenShift, es posible que debas ajustar la configuración del contexto de seguridad.

Inspeccionar los valores del chart primero

Antes de realizar la instalación, comprueba qué valores de contexto de seguridad expone el chart:

helm registry login dhi.io

helm show values oci://dhi.io/<chart-name> --version <version> | grep -A 20 securityContext

Las rutas de valores disponibles varían según el chart, así que comprueba siempre values.yaml antes de establecer sobrescripciones.

Instalar con sobrescripciones para OpenShift

El siguiente ejemplo muestra un patrón de instalación típico. Ajusta las rutas de --set según lo que devuelva helm show values para tu chart específico:

helm install my-release oci://dhi.io/<chart-name> \
    --version <version> \
    --set "imagePullSecrets[0].name=dhi-pull-secret" \
    -f openshift-values.yaml

Crea un archivo openshift-values.yaml con las sobrescripciones de contexto de seguridad adecuadas para tu chart:

# Ejemplo: ajusta las claves según el resultado del comando `helm show values`
podSecurityContext:
  runAsNonRoot: true
  seccompProfile:
    type: RuntimeDefault

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
Note

Las rutas de valores de los charts de Helm de DHI no están estandarizadas. Por ejemplo, un chart puede utilizar image.imagePullSecrets, mientras que otro utiliza global.imagePullSecrets. Consulta siempre la documentación del chart específico o su archivo values.yaml.

Verificar tu despliegue

Después de desplegar una imagen DHI en OpenShift, verifica la configuración de seguridad.

Comprobar la SCC asignada

oc get pods -o 'custom-columns=NAME:.metadata.name,SCC:.metadata.annotations.openshift\.io/scc'

Las imágenes DHI en tiempo de ejecución deberían mostrar restricted-v2 (o nonroot si lo configuraste).

Comprobar el UID en ejecución

oc exec <pod-name> -- id

Con la SCC restricted-v2, deberías ver una salida como:

uid=1000650000 gid=0(root) groups=0(root),1000650000

El UID pertenece al rango asignado del proyecto y el GID primario siempre es 0 (grupo root). Con la SCC nonroot y runAsUser: 65532, verías uid=65532.

Confirmar que la imagen es distroless

oc exec <pod-name> -- sh -c "echo hello"

Para imágenes DHI en tiempo de ejecución (no dev), este comando debería fallar con un error que indique que no se encontró sh en $PATH. El formato exacto del error varía según las versiones de CRI-O.

Escanear la imagen desplegada

Utiliza Docker Scout para verificar la postura de seguridad de la imagen desplegada (ejecuta esto desde tu máquina local, no en el clúster):

docker scout cves YOUR_ORG/dhi-nginx:1.29-alpine3.23
docker scout quickview YOUR_ORG/dhi-nginx:1.29-alpine3.23

Problemas comunes y soluciones

El pod falla al iniciarse con “container has runAsNonRoot and image has group or user ID set to root”. Esto sucede al desplegar una variante DHI -dev con la SCC restricted-v2 por defecto. Utiliza la variante de tiempo de ejecución en su lugar o concede la SCC anyuid a la cuenta de servicio.

La aplicación no puede escribir en un directorio. El UID arbitrario asignado por OpenShift no tiene permisos de escritura. Este es el problema más común con DHI en OpenShift. Todas las rutas de escritura deben pertenecer al GID 0 con permisos de escritura de grupo. Soluciona esto en la etapa de compilación: chgrp -R 0 /path && chmod -R g=u /path, luego realiza COPY --chown=<UID>:0 a la etapa de tiempo de ejecución.

La aplicación falla con “user not found” o “no matching entries in passwd file”. Algunas aplicaciones requieren una entrada /etc/passwd válida. OpenShift 4.x inyecta automáticamente el UID arbitrario en /etc/passwd en la mayoría de los casos. Si tu aplicación sigue fallando, utiliza el patrón de inyección de passwd (requiere una variante -dev) o utiliza la SCC nonroot para ejecutar el proceso con el UID integrado de la imagen.

El pod falla al vincularse al puerto 80 o 443. Los puertos inferiores al 1024 requieren privilegios de root. Las imágenes DHI utilizan puertos sin privilegios por defecto (por ejemplo, Nginx utiliza el 8080). Configura tu Servicio de OpenShift para asociar el puerto externo al puerto sin privilegios del contenedor:

apiVersion: v1
kind: Service
metadata:
  name: nginx-dhi
spec:
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: nginx-dhi

ImagePullBackOff con “unauthorized: authentication required”. Verifica que el secreto de descarga esté correctamente configurado y vinculado a la cuenta de servicio. Comprueba con oc get secret dhi-pull-secret y oc describe sa default.

La compilación del Dockerfile falla con “exec: not found” en la etapa de tiempo de ejecución. Estás utilizando RUN en una etapa de tiempo de ejecución distroless. Las imágenes de tiempo de ejecución DHI no tienen shell, por lo que los comandos RUN no se pueden ejecutar. Mueve todos los comandos RUN a la etapa de compilación -dev y utiliza COPY --chown para transferir los resultados.

Resumen de compatibilidad de DHI y OpenShift

CaracterísticaDHI runtimeDHI -devDHI con personalización Enterprise
SCC por defecto (restricted-v2)Sí, con permisos GID 0Requiere anyuidSí, con permisos GID 0
No root por defectoSí (UID 65532)No (root)Sí (UID configurable)
Soporte de UID arbitrarioSí, con chown <UID>:0Sí, con chown <UID>:0
Distroless (sin shell)Sí: sin RUN en DockerfileNoSí: sin RUN en Dockerfile
Puertos sin privilegiosSí (superiores a 1024)ConfigurableSí (superiores a 1024)
SLSA Build Nivel 3
Depuración en el clústeroc debug / contenedores efímerosoc exec con shelloc debug / contenedores efímeros

Siguientes pasos