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.
ImportantLas 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 grupononroot(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:
- Utiliza una variante DHI
-devcomo etapa de compilación (tiene una shell). - Compila tu aplicación y establece la propiedad de GID 0 en la etapa de compilación.
- 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/runImportantUtiliza 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 utilicesRUNen la etapa de tiempo de ejecución; las imágenes distroless DHI no tienen shell.
NoteEl 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-secretDHI 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-secretGestionar 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/passwdpara 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"]NotePara imágenes de tiempo de ejecución distroless (sin shell), el patrón de inyección de
passwdno es posible. En su lugar, utiliza la SCCnonroot(descrita en la siguiente sección) para ejecutar el proceso con el UID incorporado de la imagen para que la entrada existente en/etc/passwdcoincida con el proceso en ejecución. Alternativamente, OpenShift 4.x inyecta automáticamente el UID arbitrario en/etc/passwden 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.
ImportantPara que funcione la SCC
nonroot, la directivaUSERde la imagen debe especificar un UID numérico (por ejemplo,65532), no una cadena de nombre de usuario comononroot. 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, establecerunAsUserexplí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:
- ALLVerifica 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-secretImportantLa SCC
anyuidpermite 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 debuges 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:
- ALLNoteLas 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 utilizaglobal.imagePullSecrets. Consulta siempre la documentación del chart específico o su archivovalues.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),1000650000El 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-dhiImagePullBackOff 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ística | DHI runtime | DHI -dev | DHI con personalización Enterprise |
|---|---|---|---|
SCC por defecto (restricted-v2) | Sí, con permisos GID 0 | Requiere anyuid | Sí, con permisos GID 0 |
| No root por defecto | Sí (UID 65532) | No (root) | Sí (UID configurable) |
| Soporte de UID arbitrario | Sí, con chown <UID>:0 | Sí | Sí, con chown <UID>:0 |
| Distroless (sin shell) | Sí: sin RUN en Dockerfile | No | Sí: sin RUN en Dockerfile |
| Puertos sin privilegios | Sí (superiores a 1024) | Configurable | Sí (superiores a 1024) |
| SLSA Build Nivel 3 | Sí | Sí | Sí |
| Depuración en el clúster | oc debug / contenedores efímeros | oc exec con shell | oc debug / contenedores efímeros |
Siguientes pasos
- Usar una imagen en Kubernetes: guía general de despliegue de DHI en Kubernetes.
- Personalizar una imagen: añade paquetes a las imágenes DHI utilizando la personalización Enterprise.
- Depurar un contenedor: soluciona problemas en contenedores distroless con Docker Debug (desarrollo local).
- Gestionar SCCs: documentación de referencia de Red Hat sobre Security Context Constraints.
- Crear imágenes para OpenShift: directrices de Red Hat para compilar imágenes de contenedores compatibles con OpenShift.