279 lines
12 KiB
Markdown
279 lines
12 KiB
Markdown
|
## Escaneo de Seguridad
|
||
|
|
||
|
|
||
|
Cuando haya creado una imagen, es una buena práctica escanearla en busca de vulnerabilidades de seguridad utilizando el
|
||
|
comando `docker scan`.
|
||
|
Docker se ha asociado con [Snyk](http://snyk.io) para proporcionar el servicio de análisis de vulnerabilidades.
|
||
|
|
||
|
Por ejemplo, para escanear la imagen `getting-started` que creó anteriormente en el tutorial, simplemente escriba
|
||
|
|
||
|
```bash
|
||
|
docker scan getting-started
|
||
|
```
|
||
|
|
||
|
El análisis utiliza una base de datos de vulnerabilidades que se actualiza constantemente, por lo que el resultado que
|
||
|
ve variará a medida que se descubran nuevas vulnerabilidades, pero podría verse así:
|
||
|
|
||
|
```plaintext
|
||
|
✗ Low severity vulnerability found in freetype/freetype
|
||
|
Description: CVE-2020-15999
|
||
|
Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641
|
||
|
Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2
|
||
|
From: freetype/freetype@2.10.0-r0
|
||
|
From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0
|
||
|
Fixed in: 2.10.0-r1
|
||
|
|
||
|
✗ Medium severity vulnerability found in libxml2/libxml2
|
||
|
Description: Out-of-bounds Read
|
||
|
Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791
|
||
|
Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1
|
||
|
From: libxml2/libxml2@2.9.9-r3
|
||
|
From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3
|
||
|
From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3
|
||
|
Fixed in: 2.9.9-r4
|
||
|
```
|
||
|
|
||
|
El resultado enumera el tipo de vulnerabilidad, una URL para obtener más información y, lo que es más importante, qué
|
||
|
versión de la biblioteca relevante corrige la vulnerabilidad.
|
||
|
|
||
|
Hay varias otras opciones, sobre las que puede leer en [documentación docker scan](https://docs.docker.com/engine/scan/).
|
||
|
|
||
|
Además de escanear su imagen recién construida en la línea de comando, también puede
|
||
|
[configurar Docker Hub](https://docs.docker.com/docker-hub/vulnerability-scanning/)
|
||
|
para escanear todas las imágenes recién enviadas automáticamente, y luego puede ver los resultados tanto en Docker Hub
|
||
|
como en Docker Desktop.
|
||
|
|
||
|
![Escaneo de vulnerabilidades del hub](hvs.png){: style=width:75% }
|
||
|
{: .text-center }
|
||
|
|
||
|
## Capas de imagen
|
||
|
|
||
|
¿Sabías que puedes mirar lo que compone una imagen? Usando el comando `docker image history`,
|
||
|
puede ver el comando que se usó para crear cada capa dentro de una imagen.
|
||
|
|
||
|
1. Utilice el comando `docker image history` para ver las capas en la imagen de `getting-started` que creó
|
||
|
anteriormente en el tutorial.
|
||
|
|
||
|
```bash
|
||
|
docker image history getting-started
|
||
|
```
|
||
|
|
||
|
Debería obtener un resultado que se parezca a esto (los identificadores de fechas pueden ser diferentes).
|
||
|
|
||
|
```plaintext
|
||
|
IMAGE CREATED CREATED BY SIZE COMMENT
|
||
|
a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0B
|
||
|
f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB
|
||
|
a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB
|
||
|
9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B
|
||
|
b95baba1cfdb 13 days ago /bin/sh -c #(nop) CMD ["node"] 0B
|
||
|
<missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
|
||
|
<missing> 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B
|
||
|
<missing> 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB
|
||
|
<missing> 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B
|
||
|
<missing> 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB
|
||
|
<missing> 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B
|
||
|
<missing> 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
|
||
|
<missing> 13 days ago /bin/sh -c #(nop) ADD file:e69d441d729412d24… 5.59MB
|
||
|
```
|
||
|
|
||
|
Cada una de las líneas representa una capa en la imagen. La pantalla aquí muestra la base en la parte inferior con
|
||
|
la capa más nueva en la parte superior. Con esto, también puede ver rápidamente el tamaño de cada capa, lo que ayuda
|
||
|
a diagnosticar imágenes grandes.
|
||
|
|
||
|
1. Notarás que varias de las líneas están truncadas. Si agrega el indicador `--no-trunc`, obtendrá la salida completa
|
||
|
(sí ... es curioso cómo usa una bandera truncada para obtener una salida no truncada, ¿eh?)
|
||
|
|
||
|
```bash
|
||
|
docker image history --no-trunc getting-started
|
||
|
```
|
||
|
|
||
|
|
||
|
## Almacenamiento en caché de capas
|
||
|
|
||
|
Ahora que ha visto las capas en acción, hay una lección importante que aprender para ayudar a reducir los build
|
||
|
times de las imágenes de su contenedor.
|
||
|
|
||
|
> Una vez que cambia una capa, todas las capas posteriores también deben volver a crearse
|
||
|
|
||
|
Veamos el Dockerfile que estábamos usando una vez más ...
|
||
|
|
||
|
```dockerfile
|
||
|
FROM node:12-alpine
|
||
|
WORKDIR /app
|
||
|
COPY . .
|
||
|
RUN yarn install --production
|
||
|
CMD ["node", "src/index.js"]
|
||
|
```
|
||
|
|
||
|
Volviendo a la salida del historial de la imagen, vemos que cada comando en el Dockerfile se convierte en una nueva capa
|
||
|
en la imagen. Quizás recuerde que cuando hicimos un cambio en la imagen, las dependencias yarn tuvieron que reinstalarse.
|
||
|
¿Hay alguna forma de solucionar este problema? No tiene mucho sentido distribuir las mismas dependencias cada vez que
|
||
|
construimos, ¿verdad?
|
||
|
|
||
|
Para solucionar esto, necesitamos reestructurar nuestro Dockerfile para ayudar a soportar el almacenamiento en caché de
|
||
|
las dependencias. Para aplicaciones Node-based, esas dependencias se definen en el archivo `package.json`. Entonces,
|
||
|
¿qué pasa si copiamos solo ese archivo primero, instalamos las dependencias y, luego, copiamos todo lo demás? Luego,
|
||
|
solo recreamos las dependencias yarn si hubo un cambio en el `package.json`. ¿Tener sentido?
|
||
|
|
||
|
1. Actualice el Dockerfile para copiar en el `package.json` primero, instale las dependencias y luego copie todo lo
|
||
|
demás en.
|
||
|
|
||
|
```dockerfile hl_lines="3 4 5"
|
||
|
FROM node:12-alpine
|
||
|
WORKDIR /app
|
||
|
COPY package.json yarn.lock ./
|
||
|
RUN yarn install --production
|
||
|
COPY . .
|
||
|
CMD ["node", "src/index.js"]
|
||
|
```
|
||
|
|
||
|
1. Cree un archivo llamado `.dockerignore` en la misma carpeta que Dockerfile con el siguiente contenido.
|
||
|
|
||
|
```ignore
|
||
|
node_modules
|
||
|
```
|
||
|
|
||
|
Los archivos `.dockerignore` son una manera fácil de copiar selectivamente solo archivos relevantes de imagen.
|
||
|
Puede leer más sobre esto
|
||
|
[aquí](https://docs.docker.com/engine/reference/builder/#dockerignore-file).
|
||
|
En este caso, la carpeta `node_modules` debe omitirse en el segundo paso `COPY` porque de lo contrario,
|
||
|
posiblemente sobrescribirá los archivos que fueron creados por el comando en el paso `RUN`.
|
||
|
Para obtener más detalles sobre por qué se recomienda esto para las aplicaciones Node.js y otras prácticas
|
||
|
recomendadas, echa un vistazo a su guía en
|
||
|
[Dockerizar una aplicación web Node.js](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/).
|
||
|
|
||
|
1. Cree una nueva imagen usando `docker build`.
|
||
|
|
||
|
```bash
|
||
|
docker build -t getting-started .
|
||
|
```
|
||
|
|
||
|
Debería ver un resultado como este ...
|
||
|
|
||
|
```plaintext
|
||
|
Sending build context to Docker daemon 219.1kB
|
||
|
Step 1/6 : FROM node:12-alpine
|
||
|
---> b0dc3a5e5e9e
|
||
|
Step 2/6 : WORKDIR /app
|
||
|
---> Using cache
|
||
|
---> 9577ae713121
|
||
|
Step 3/6 : COPY package.json yarn.lock ./
|
||
|
---> bd5306f49fc8
|
||
|
Step 4/6 : RUN yarn install --production
|
||
|
---> Running in d53a06c9e4c2
|
||
|
yarn install v1.17.3
|
||
|
[1/4] Resolving packages...
|
||
|
[2/4] Fetching packages...
|
||
|
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
|
||
|
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
|
||
|
[3/4] Linking dependencies...
|
||
|
[4/4] Building fresh packages...
|
||
|
Done in 10.89s.
|
||
|
Removing intermediate container d53a06c9e4c2
|
||
|
---> 4e68fbc2d704
|
||
|
Step 5/6 : COPY . .
|
||
|
---> a239a11f68d8
|
||
|
Step 6/6 : CMD ["node", "src/index.js"]
|
||
|
---> Running in 49999f68df8f
|
||
|
Removing intermediate container 49999f68df8f
|
||
|
---> e709c03bc597
|
||
|
Successfully built e709c03bc597
|
||
|
Successfully tagged getting-started:latest
|
||
|
```
|
||
|
|
||
|
Verá que se reconstruyeron todas las capas. Perfectamente bien desde que cambiamos bastante el Dockerfile.
|
||
|
|
||
|
1. Ahora, haga un cambio en el archivo `src/static/index.html` (como cambiar el `<title>` para decir "The Awesome Todo App").
|
||
|
|
||
|
1. Cree la imagen de Docker ahora usando `docker build -t Getting started .` nuevamente. Esta vez, su salida debería
|
||
|
verse un poco diferente.
|
||
|
|
||
|
```plaintext hl_lines="5 8 11"
|
||
|
Sending build context to Docker daemon 219.1kB
|
||
|
Step 1/6 : FROM node:12-alpine
|
||
|
---> b0dc3a5e5e9e
|
||
|
Step 2/6 : WORKDIR /app
|
||
|
---> Using cache
|
||
|
---> 9577ae713121
|
||
|
Step 3/6 : COPY package.json yarn.lock ./
|
||
|
---> Using cache
|
||
|
---> bd5306f49fc8
|
||
|
Step 4/6 : RUN yarn install --production
|
||
|
---> Using cache
|
||
|
---> 4e68fbc2d704
|
||
|
Step 5/6 : COPY . .
|
||
|
---> cccde25a3d9a
|
||
|
Step 6/6 : CMD ["node", "src/index.js"]
|
||
|
---> Running in 2be75662c150
|
||
|
Removing intermediate container 2be75662c150
|
||
|
---> 458e5c6f080c
|
||
|
Successfully built 458e5c6f080c
|
||
|
Successfully tagged getting-started:latest
|
||
|
```
|
||
|
|
||
|
Antes que nada, ¡Deberías notar que la construcción fue MUCHO más rápida! Y verá que todos los pasos 1-4 tienen
|
||
|
"Using cache". Entonces, ¡hurra! Estamos usando la caché de compilación. Pushing y pulling esta imagen y las
|
||
|
actualizaciones también será mucho más rápido. ¡Hurra!
|
||
|
|
||
|
|
||
|
## Construcciones Multi-Stage
|
||
|
|
||
|
Si bien no vamos a profundizar demasiado en este tutorial, las compilaciones multi-stage son una herramienta
|
||
|
increíblemente poderosa para ayudar a usar multiple stages para crear una imagen. Tienen varias ventajas:
|
||
|
|
||
|
- Separe las dependencias en build-time de las dependencias en runtime
|
||
|
- Reduzca el tamaño general de la imagen enviando _solo_ lo que su aplicación necesita para ejecutarse
|
||
|
|
||
|
### Ejemplo Maven/Tomcat
|
||
|
|
||
|
Al crear aplicaciones basadas en Java, se necesita un JDK para compilar el código fuente en código de bytes Java.
|
||
|
Sin embargo, ese JDK no es necesario en producción. Además, es posible que esté utilizando herramientas como Maven o
|
||
|
Gradle para ayudar a crear la aplicación. Esos tampoco son necesarios en nuestra imagen final. Las compilaciones de
|
||
|
varias etapas ayudan.
|
||
|
|
||
|
```dockerfile
|
||
|
FROM maven AS build
|
||
|
WORKDIR /app
|
||
|
COPY . .
|
||
|
RUN mvn package
|
||
|
|
||
|
FROM tomcat
|
||
|
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
|
||
|
```
|
||
|
|
||
|
En este ejemplo, usamos una etapa (llamada `build`) para realizar la compilación real de Java usando Maven. En la segunda
|
||
|
etapa (comenzando en `FROM tomcat`), copiamos archivos de la etapa de `build`. La imagen final es solo la última etapa
|
||
|
que se está creando (que se puede anular usando el indicador `--target`).
|
||
|
|
||
|
|
||
|
### Ejemplo React
|
||
|
|
||
|
Al crear aplicaciones React, necesitamos un entorno Node para compilar el código JS (normalmente JSX), SASS stylesheets,
|
||
|
y más en HTML, JS, y CSS estático. Si no estamos haciendo renderizado del lado del servidor, ni siquiera necesitamos
|
||
|
un entorno Node para nuestra compilación de producción. ¿Por qué no enviar los recursos estáticos en un contenedor nginx
|
||
|
estático?
|
||
|
|
||
|
```dockerfile
|
||
|
FROM node:12 AS build
|
||
|
WORKDIR /app
|
||
|
COPY package* yarn.lock ./
|
||
|
RUN yarn install
|
||
|
COPY public ./public
|
||
|
COPY src ./src
|
||
|
RUN yarn run build
|
||
|
|
||
|
FROM nginx:alpine
|
||
|
COPY --from=build /app/build /usr/share/nginx/html
|
||
|
```
|
||
|
|
||
|
Aquí, estamos usando una imagen `node: 12` para realizar la compilación (maximizando el almacenamiento en caché de la
|
||
|
capa) y luego copiando la salida en un contenedor nginx. Genial, ¿eh?
|
||
|
|
||
|
|
||
|
## Resumen
|
||
|
|
||
|
Al comprender un poco cómo se estructuran las imágenes, podemos crear imágenes más rápido y enviar menos cambios.
|
||
|
El escaneo de imágenes nos da la confianza de que los contenedores que estamos ejecutando y distribuyendo son seguros.
|
||
|
Las compilaciones Multi-stage también nos ayudan a reducir el tamaño general de la imagen y a aumentar la seguridad del
|
||
|
contenedor final al separar las dependencias en build-time de las dependencias en runtime.
|