Esta publicación es uno de los cuatro tutoriales que te ayudan a poner en práctica los conceptos de Microservicios de marzo de 2023: Comience a entregar microservicios :
Muchos de sus microservicios necesitan secretos para funcionar de forma segura. Algunos ejemplos de secretos incluyen la clave privada para un certificado SSL/TLS, una clave API para autenticarse en otro servicio o una clave SSH para iniciar sesión de forma remota. Una gestión adecuada de secretos requiere limitar estrictamente los contextos en los que se utilizan únicamente a los lugares donde es necesario y evitar que se acceda a ellos excepto cuando sea necesario. Pero esta práctica a menudo se omite en las prisas del desarrollo de aplicação . ¿El resultado? La gestión inadecuada de secretos es una causa común de fugas de información y explotación.
En este tutorial, mostramos cómo distribuir y usar de forma segura un JSON Web Token (JWT) que un contenedor de cliente utiliza para acceder a un servicio. En los cuatro desafíos de este tutorial, experimentarás con cuatro métodos diferentes para administrar secretos, para aprender no solo cómo administrar los secretos correctamente en tus contenedores, sino también sobre los métodos que son inadecuados:
Si bien este tutorial utiliza un JWT como ejemplo de secreto, las técnicas se aplican a cualquier cosa que necesite mantener en secreto en los contenedores, como credenciales de base de datos, claves privadas SSL y otras claves API.
El tutorial aprovecha dos componentes de software principales:
GET
al servidor APIMire este vídeo para ver una demostración del tutorial en acción.
La forma más sencilla de realizar este tutorial es registrarse en Microservices March y utilizar el laboratorio basado en navegador que se proporciona. Esta publicación proporciona instrucciones para ejecutar el tutorial en su propio entorno.
Para completar el tutorial en su propio entorno, necesitará:
nano
o vim
curl
(ya instalado en la mayoría de los sistemas)git
(ya instalado en la mayoría de los sistemas)Notas:
-p
para establecer un valor diferente para el servidor de prueba cuando lo inicie con el comando docker
run
. Luego incluya el :<número_de_puerto>
sufijo en host local
en el rizo
comandos.~
) representa su directorio de inicio.En esta sección , clonará el repositorio del tutorial , iniciará el servidor de autenticación y enviará solicitudes de prueba con y sin un token.
En su directorio de inicio, cree el directorio microservices-march y clone el repositorio de GitHub en él. (También puede utilizar un nombre de directorio diferente y adaptar las instrucciones en consecuencia). El repositorio incluye archivos de configuración y versiones separadas de la aplicação cliente API que utilizan diferentes métodos para obtener secretos.
mkdir ~/microservices-marchcd ~/microservices-march
git clone https://github.com/microservices-march/auth.git
Mostrar el secreto. Es un JWT firmado, comúnmente utilizado para autenticar clientes API en servidores.
gato ~/microservicios-marzo/auth/apiclient/token1.jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2Nz UyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA"
Si bien hay algunas formas de usar este token para la autenticación, en este tutorial la aplicación cliente API lo pasa al servidor de autenticación mediante el marco de autorización de token de portador OAuth 2.0 . Esto implica anteponer Autorización al JWT:
Portador
como en este ejemplo:
"Autorización: Portador eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA"
Cambiar al directorio del servidor de autenticación:
servidor de cd
Construya la imagen de Docker para el servidor de autenticación (tenga en cuenta el período final):
docker build -t apiserver .
Inicie el servidor de autenticación y confirme que esté en ejecución (la salida se distribuye en varias líneas para facilitar su lectura):
docker run -d -p 80:80 apiserver docker ps ID DEL CONTENEDOR IMAGEN COMANDO ...
2b001f77c5cb apiserver "nginx -g 'demonio de..." ... ... ESTADO CREADO ... ... Hace 26 segundos Arriba 26 segundos ... ... PUERTOS ... ... 0.0.0.0:80->80/tcp, :::80->80/tcp, 443/tcp ... ... NOMBRES... relajado_proskuriakova
Verifique que el servidor de autenticación rechace una solicitud que no incluya el JWT y devuelva 401
Autorización
requerida
:
curl -X GET http://localhost<html>
<head><title>Se requiere autorización 401</title></head>
<body>
<center><h1>Se requiere autorización 401</h1></center>
<hr><center>nginx/1.23.3</center>
</body>
</html>
Proporcione el JWT utilizando el encabezado de autorización
. El200
El código de retorno OK
indica que la aplicación cliente API se autenticó exitosamente.
curl -i -X GET -H "Autorización: Portador `cat $HOME/microservices-march/auth/apiclient/token1.jwt`" http://localhost HTTP/1.1 200 OK Servidor: nginx/1.23.2 Fecha: Día , DD Lun AAAA hh : mm : ss TZ Tipo de contenido: texto/html Longitud del contenido: 64 Última modificación: Día , DD Lun AAAA hh : mm : ss TZ Conexión: keep-alive ETag: "63dc0fcd-40" MENSAJE X: apiKey1 de éxito Accept-Ranges: bytes { "respuesta": "éxito", "autorizado": verdadero, "valor": "999" }
Antes de comenzar este desafío, seamos claros: ¡codificar secretos en tu aplicación es una terrible idea! Verá cómo cualquier persona con acceso a la imagen del contenedor puede encontrar y extraer fácilmente credenciales codificadas.
En este desafío, copia el código de la aplicación cliente API en el directorio de compilación, compila y ejecuta la aplicación y extrae el secreto .
El subdirectorio app_versions del directorio apiclient contiene diferentes versiones de la aplicación cliente API simple para los cuatro desafíos, cada una ligeramente más segura que la anterior (consulte la Descripción general del tutorial para obtener más información).
Cambiar al directorio del cliente API:
cd ~/microservicios-march/auth/apiclient
Copia la aplicación para este desafío (la que tiene un secreto codificado) al directorio de trabajo:
cp ./app_versions/código_duro_muy_malo.py ./app.py
Echa un vistazo a la aplicación:
cat app.py importar urllib.solicitud importar urllib.error jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA" authstring = "Portador " + jwt req = urllib.solicitud.Solicitud("http://host.docker.internal") req.add_header("Autorización", authstring) intentar: con urllib.request.urlopen(req) como respuesta: la_pagina = respuesta.read() mensaje = respuesta.getheader("X-MESSAGE") imprimir("200 " + mensaje) excepto urllib.error.URLError como e: imprimir(str(e.code) + " s " + e.msg)
El código simplemente realiza una solicitud a un host local e imprime un mensaje de éxito o un código de error.
La solicitud agrega el encabezado de Autorización
en esta línea:
req.add_header("Autorización", cadena de autenticación)
¿Notas algo más? ¿Quizás un JWT codificado? Llegaremos a eso en un minuto. Primero, construyamos y ejecutemos la aplicación.
Estamos usando el comando docker
compose
junto con un archivo YAML de Docker Compose: esto hace que sea un poco más fácil entender qué está sucediendo.
(Tenga en cuenta que en el Paso 2 de la sección anterior cambió el nombre del archivo Python para la aplicación cliente API específica del Desafío 1 ( very_bad_hard_code.py ) a app.py. También harás esto en los otros tres desafíos. Usar app.py cada vez simplifica la logística porque no es necesario cambiar el Dockerfile . Esto significa que debes incluir el argumento ‑build
en el comando docker
compose
para forzar una reconstrucción del contenedor cada vez).
El comando docker
compose
crea el contenedor, inicia la aplicação, realiza una única solicitud de API y luego apaga el contenedor, mientras muestra los resultados de la llamada de API en la consola.
El200
El código de éxito
en la segunda línea antes de la última de la salida indica que la autenticación se realizó correctamente. El valor apiKey1
es una confirmación adicional, ya que muestra que el servidor de autenticación pudo decodificar la reclamación de ese nombre en el JWT:
docker compose -f docker-compose.hardcode.yml up -build ... apiclient-apiclient-1 | 200 Éxito apiKey1 apiclient-apiclient-1 salió con el código 0
Entonces, las credenciales codificadas funcionaron correctamente para nuestra aplicación cliente API, lo cual no es sorprendente. ¿Pero es seguro? ¿Quizás sea así, ya que el contenedor ejecuta este script solo una vez antes de salir y no tiene un shell?
De hecho, no, no es seguro en absoluto.
La codificación rígida de las credenciales deja abiertas a la inspección de cualquiera que pueda acceder a la imagen del contenedor, porque extraer el sistema de archivos de un contenedor es un ejercicio trivial.
Crea el directorio de extracción y cámbialo:
mkdir extractcd extraer
Enumere información básica sobre las imágenes del contenedor. El indicador --format
hace que la salida sea más legible (y la salida se distribuye en dos líneas aquí por la misma razón):
docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}" CONTENEDOR ID NOMBRES IMAGEN ...
11b73106fdf8 apiclient-apiclient-1 apiclient ... ad9bdc05b07c emocionante_clarke apiserver ... ... ESTADO CREADO... Hace 6 minutos Salió (0) Hace 4 minutos ... Hace 43 minutos Arriba 43 minutos
Extraiga la imagen de apiclient más reciente como un archivo .tar . Para <ID del contenedor>
, sustituya el valor de la RECIPIENTE
IDENTIFICACIÓN
campo en la salida anterior (11b73106fdf8
en este tutorial):
docker export -o api.tar <ID del contenedor>
Se necesitan unos segundos para crear el archivo api.tar , que incluye todo el sistema de archivos del contenedor. Un enfoque para encontrar secretos es extraer todo el archivo y analizarlo, pero resulta que hay un atajo para encontrar lo que probablemente sea interesante: mostrar el historial del contenedor con el comando docker
history
. (Este atajo es especialmente útil porque también funciona para contenedores que encuentre en Docker Hub u otro registro de contenedores y, por lo tanto, es posible que no tengan el Dockerfile , sino solo la imagen del contenedor).
Mostrar el historial del contenedor:
Historial de Docker Apclient Imagen creada...
9396dde2aad0 Hace 8 minutos ... Hace 8 minutos ... Hace 28 minutos ... ... CREADO POR TAMAÑO... ... CMD ["python" "./app.py"] 622B ... ... COPIA ./app.py ./app.py # buildkit 0B ... ... WORKDIR /usr/app/src 0B ... ... COMENTARIO... buildkit.dockerfile.v0... buildkit.dockerfile.v0... buildkit.dockerfile.v0
Las líneas de salida están en orden cronológico inverso. Muestran que el directorio de trabajo se configuró en /usr/app/src y luego se copió y ejecutó el archivo de código Python para la aplicación. No hace falta ser un gran detective para deducir que el código base principal de este contenedor está en /usr/app/src/app.py y, como tal, es una ubicación probable para las credenciales.
Armado con ese conocimiento, extraiga solo ese archivo:
tar --extract --file=api.tar usr/app/src/app.py
Mostramos el contenido del archivo y, así de fácil, hemos obtenido acceso al JWT “seguro”:
gato usr/app/src/app.py ... jwt="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA" ...
Si completó la Unidad 1 de Microservicios de marzo de 2023 (Aplicar la aplicación de doce factores a las arquitecturas de microservicios), está familiarizado con el uso de variables de entorno para pasar datos de configuración a contenedores. Si te lo perdiste, no te preocupes: estará disponible a pedido después de registrarte .
En este desafío, pasa secretos como variables de entorno. Al igual que el método del Desafío 1 , ¡no recomendamos este! No es tan malo como codificar secretos, pero como verás, tiene algunas debilidades.
Hay cuatro formas de pasar variables de entorno a un contenedor:
Utilice la declaración ENV
en un Dockerfile para realizar la sustitución de variables (establecer la variable para todas las imágenes creadas). Por ejemplo:
PUERTO ENV $PUERTO
Utilice la bandera -e
en el comando docker
run
. Por ejemplo:
docker run -e CONTRASEÑA=123 micontenedor
de entorno
en un archivo YAML de Docker Compose.En este desafío, utiliza una variable de entorno para configurar el JWT y examina el contenedor para ver si el JWT está expuesto.
Regrese al directorio del cliente API:
cd ~/microservicios-march/auth/apiclient
Copie la aplicación para este desafío (la que usa variables de entorno) al directorio de trabajo, sobrescribiendo el archivo app.py del Desafío 1:
cp ./app_versions/variables_de_entorno_medio.py ./app.py
Eche un vistazo a la aplicación. En las líneas de salida relevantes, el secreto (JWT) se lee como una variable de entorno en el contenedor local:
cat app.py ... jwt = "" si "JWT" en os.environ: jwt = "Portador " + os.environ.get("JWT") ...
Como se explicó anteriormente, hay varias formas de introducir la variable de entorno en el contenedor. Para mantener la coherencia, seguiremos utilizando Docker Compose. Muestra el contenido del archivo YAML de Docker Compose, que utiliza la clave de entorno
para establecer la variable de entorno JWT
:
gato docker-compose.env.yml --- versión: Servicios "3.9": apiclient: compilación: . imagen: apiclient extra_hosts: - "host.docker.internal:host-gateway" entorno: - JWT
Ejecute la aplicación sin configurar la variable de entorno. El401
El código no autorizado
en la segunda línea antes de la última de la salida confirma que la autenticación falló porque la aplicación cliente de API no pasó el JWT:
docker compose -f docker-compose.env.yml up -build ... apiclient-apiclient-1 | 401 No autorizado apiclient-apiclient-1 salió con el código 0
Para simplificar, configure la variable de entorno localmente. Está bien hacer eso en este punto del tutorial, ya que no es el problema de seguridad que nos preocupa en este momento:
exportar JWT=`cat token1.jwt`
Ejecute el contenedor nuevamente. Ahora la prueba tiene éxito, con el mismo mensaje que en el Desafío 1:
docker compose -f docker-compose.env.yml up -build ... apiclient-apiclient-1 | 200 Éxito apiKey1 apiclient-apiclient-1 salió con el código 0
Así que al menos ahora la imagen base no contiene el secreto y podemos pasarla en tiempo de ejecución, lo cual es más seguro. Pero todavía hay un problema.
Muestra información sobre las imágenes del contenedor para obtener el ID del contenedor para la aplicación cliente de API (la salida se distribuye en dos líneas para facilitar la legibilidad):
docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}" CONTENEDOR ID NOMBRES IMAGEN ...
6b20c75830df apiclient-apiclient-1 apiclient ... ad9bdc05b07c emocionante_clarke apiserver ... ... ESTADO CREADO... Hace 6 minutos Salió (0) Hace 6 minutos ... Hace aproximadamente una hora Arriba Hace aproximadamente una hora
Inspeccione el contenedor para la aplicación cliente de API. Para <ID del contenedor>
, sustituya el valor de la RECIPIENTE
IDENTIFICACIÓN
campo en la salida anterior (aquí 6b20c75830df
).
El comando docker
inspect
le permite inspeccionar todos los contenedores iniciados, ya sea que se estén ejecutando o no. Y ese es el problema: aunque el contenedor no se esté ejecutando, la salida expone el JWT en la matriz Env
, guardado de forma insegura en la configuración del contenedor.
inspección de Docker <ID del contenedor>...
"Env": [ "JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA...", "RUTA=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "LANG=C.UTF-8", "CLAVE_GPG=A035C8C19219BA821ECEA86B64E628F8D684696D", "VERSIÓN_PYTHON=3.11.2", "VERSIÓN_PIP_PYTHON=22.3.1", "VERSIÓN_HERRAMIENTAS_DE_CONFIGURACIÓN_PYTHON=65.5.1", URL de PYTHON_GET_PIP=https://github.com/pypa/get-pip/raw/1a96dc5acd0303c4700e026...", "SHA256 de PYTHON_GET_PIP=d1d09b0f9e745610657a528689ba3ea44a73bd19c60f4c954271b790c..."]
A esta altura ya debe saber que codificar secretos y usar variables de entorno no es tan seguro como usted (o su equipo de seguridad) necesita.
Para mejorar la seguridad, puedes intentar usar secretos locales de Docker para almacenar información confidencial. Nuevamente, este no es el método estándar de oro, pero es bueno entender cómo funciona. Incluso si no usas Docker en producción, lo importante es saber cómo puedes dificultar la extracción del secreto de un contenedor.
En Docker, los secretos se exponen a un contenedor a través del sistema de archivos mount /run/secrets/, donde hay un archivo separado que contiene el valor de cada secreto.
En este desafío , pasa un secreto almacenado localmente al contenedor mediante Docker Compose y luego verifica que el secreto no sea visible en el contenedor cuando se usa este método.
Como ya te puedes imaginar, empieza cambiando al directorio apiclient :
cd ~/microservicios-march/auth/apiclient
Copia la aplicación para este desafío (la que usa secretos desde dentro de un contenedor) al directorio de trabajo, sobrescribiendo el archivo app.py del Desafío 2:
cp ./app_versions/better_secrets.py ./app.py
Eche un vistazo al código Python, que lee el valor JWT del archivo /run/secrets/jot . (Y sí, probablemente deberíamos comprobar que el archivo solo tenga una línea. ¿Tal vez en Microservicios en marzo de 2024?)
cat app.py ... jotfile = "/run/secrets/jot" jwt = "" si os.path.isfile(jotfile): con open(jotfile) como jwtfile: para línea en jwtfile: jwt = "Portador " + línea ...
Bien, entonces ¿cómo vamos a crear este secreto? La respuesta está en el archivo docker-compose.secrets.yml .
Eche un vistazo al archivo Docker Compose, donde el archivo secreto se define en la sección de secretos
y luego es referenciado por el servicio apiclient
:
gato docker-compose.secrets.yml --- versión: Secretos "3.9": jot: archivo: token1.jwt servicios: apiclient: compilación: . extra_hosts: - "host.docker.internal:host-gateway" secretos: - jot
Ejecute la aplicación. Dado que el JWT es accesible dentro del contenedor, la autenticación se realiza correctamente con el mensaje ya conocido:
docker compose -f docker-compose.secrets.yml up -build ... apiclient-apiclient-1 | 200 Éxito apiKey1 apiclient-apiclient-1 salió con el código 0
Muestra información sobre las imágenes del contenedor, anotando el ID del contenedor para la aplicación cliente de API (para ver un ejemplo de salida, consulta el Paso 1 en Examinar el contenedor del Desafío 2):
docker ps -a --format "tabla {{.ID}}\t{{.Nombres}}\t{{.Imagen}}\t{{.RunningFor}}\t{{.Estado}}"
Inspeccione el contenedor para la aplicación cliente de API. Para <ID del contenedor>
, sustituya el valor de la RECIPIENTE
IDENTIFICACIÓN
campo en la salida del paso anterior. A diferencia de la salida del Paso 2 de Examinar el contenedor , no hay ninguna línea JWT=
al comienzo de la sección Env
:
inspección de Docker <ID del contenedor>
"Env": [
"RUTA=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=C.UTF-8",
"CLAVE_GPG=A035C8C19219BA821ECEA86B64E628F8D684696D",
"VERSIÓN_PYTHON=3.11.2",
"VERSIÓN_PIP_PYTHON=22.3.1",
"VERSIÓN_HERRAMIENTAS_DE_CONFIGURACIÓN_PYTHON=65.5.1",
"URL_GET_PIP_PYTHON=https://github.com/pypa/get-pip/raw/1a96dc5acd0303c4700e026...", "PYTHON_GET_PIP_SHA256=d1d09b0f9e745610657a528689ba3ea44a73bd19c60f4c954271b790c..."
Hasta ahora todo bien, pero nuestro secreto está en el sistema de archivos contenedor en /run/secrets/jot . Tal vez podamos extraerlo de allí usando el mismo método que en Recuperar el secreto de la imagen del contenedor del desafío 1.
Cambie al directorio de extracción (que creó durante el Desafío 1) y exporte el contenedor a un archivo tar :
cd extractdocker export -o api2.tar <ID del contenedor>
Busque el archivo de secretos en el archivo tar :
tar tvf api2.tar | grep jot -rwxr-xr-x 0 0 0 0 Mon DD hh :mm ejecutar/secretos/jot
Uh oh, el archivo con el JWT está visible. ¿No dijimos que incrustar secretos en el contenedor era “seguro”? ¿Las cosas están tan mal como en el Desafío 1?
Veamos: extraiga el archivo de secretos del archivo tar y observe su contenido:
tar --extract --file=api2.tar ejecutar/secretos/jotcat ejecutar/secretos/jot
¡Albricias! No hay salida del comando cat
, lo que significa que el archivo run/secrets/jot en el sistema de archivos contenedor está vacío: ¡no hay ningún secreto para ver allí! Incluso si hay un artefacto secreto en nuestro contenedor, Docker es lo suficientemente inteligente como para no almacenar ningún dato confidencial en el contenedor.
Dicho esto, aunque esta configuración de contenedor es segura, tiene un inconveniente. Depende de la existencia de un archivo llamado token1.jwt en el sistema de archivos local cuando ejecuta el contenedor. Si cambia el nombre del archivo, fallará el intento de reiniciar el contenedor. (Puedes probar esto tú mismo renombrando [¡no eliminando!] token1.jwt y ejecutando el comando docker
compose
del Paso 1 nuevamente).
Así que estamos a mitad de camino: el contenedor usa secretos de una forma que los protege contra una fácil vulneración, pero el secreto todavía no está protegido en el host. No quieres que los secretos se almacenen sin cifrar en un archivo de texto sin formato. Es hora de implementar una herramienta de gestión de secretos.
Un administrador de secretos le ayuda a administrar, recuperar y rotar secretos a lo largo de sus ciclos de vida. Hay muchos administradores de secretos para elegir y todos cumplen un propósito similar:
Sus opciones para la gestión de secretos incluyen:
Para simplificar, este desafío utiliza Docker Swarm, pero los principios son los mismos para muchos administradores de secretos.
En este desafío, crea un secreto en Docker , copia el secreto y el código del cliente API , implementa el contenedor , ve si puedes extraer el secreto y rota el secreto .
Como ya es tradición, cambie al directorio apiclient :
cd ~/microservicios-march/auth/apiclient
Inicializar Docker Swarm:
docker swarm init Swarm inicializado: el nodo actual (t0o4eix09qpxf4ma1rrs9omrm) ahora es un administrador. ...
Crea un secreto y guárdalo en token1.jwt :
docker secret crea jot ./token1.jwt qe26h73nhb35bak5fr5east27
Mostrar información sobre el secreto. Tenga en cuenta que el valor secreto (el JWT) no se muestra:
Inspeccionar secreto de Docker Jot [ { "ID": "qe26h73nhb35bak5fr5east27", "Versión": { "Índice": 11 }, "CreadoEn": " AAAA - MM - DD T hh : mm : ss . ms Z", "Actualizado a las": " AAAA - MM - DD T hh : mm : ss . ms Z", "Especificación": { "Nombre": "jot", "Etiquetas": {} } } ]
Usar el secreto de Docker en el código de la aplicação cliente API es exactamente lo mismo que usar un secreto creado localmente: puedes leerlo desde el sistema de archivos /run/secrets/ . Todo lo que necesita hacer es cambiar el calificador secreto en su archivo YAML de Docker Compose.
Eche un vistazo al archivo YAML de Docker Compose. Tenga en cuenta el valor verdadero
en el campo externo
, lo que indica que estamos usando un secreto de Docker Swarm:
gato docker-compose.secretmgr.yml --- versión: "3.9" secretos: jot: externo: verdadero servicios: apiclient: compilación: . imagen: apiclient extra_hosts: - "host.docker.internal:host-gateway" secretos: - jot
Por lo tanto, podemos esperar que este archivo Compose funcione con nuestro código de aplicação cliente API existente. Bueno, casi. Si bien Docker Swarm (o cualquier otra plataforma de orquestación de contenedores) aporta mucho valor adicional, también implica cierta complejidad adicional.
Dado que Docker
Compose
no funciona con secretos externos, tendremos que usar algunos comandos de Docker Swarm, en particular Docker
Stack
Deploy
. Docker Stack oculta la salida de la consola, por lo que tenemos que escribir la salida en un registro y luego inspeccionarlo.
Para facilitar las cosas, también utilizamos un bucle while
True
continuo para mantener el contenedor en ejecución.
Copie la aplicación para este desafío (la que usa un administrador de secretos) al directorio de trabajo, sobrescribiendo el archivo app.py del Desafío 3. Al mostrar el contenido de app.py , vemos que el código es casi idéntico al código del Desafío 3. La única diferencia es la adición del bucle while
True
:
cp ./app_versions/best_secretmgr.py ./app.pycat ./app.py ... mientras sea verdadero: time.sleep(5) intentar: con urllib.request.urlopen(req) como respuesta: the_page = response.read() mensaje = response.getheader("X-MESSAGE") imprimir("200 " + mensaje, archivo=sys.stderr) excepto urllib.error.URLError como e: imprimir(str(e.code) + " " + e.msg, archivo=sys.stderr)
Construya el contenedor (en desafíos anteriores, Docker Compose se encargó de esto):
docker build -t apiclient .
Implementar el contenedor:
docker stack deploy --compose-file docker-compose.secretmgr.yml secretstack Creando la red secretstack_default Creando el servicio secretstack_apiclient
Enumere los contenedores en ejecución, anotando el ID del contenedor para secretstack_apiclient (como antes, la salida se distribuye en varias líneas para facilitar la lectura).
docker ps --format "tabla {{.ID}}\t{{.Nombres}}\t{{.Imagen}}\t{{.RunningFor}}\t{{.Estado}}" ID DEL CONTENEDOR ...
20d0c83a8b86 ... ad9bdc05b07c ... ... NOMBRES ... ... secretstack_apiclient.1.0e9s4mag5tadvxs6op6lk8vmo ... ... emocionante_clarke ... ... ESTADO DE CREACIÓN DE LA IMAGEN... apiclient:latest Hace 31 segundos Activa hace 30 segundos... apiserver Hace 2 horas Activa hace 2 horas
Mostrar el archivo de registro de Docker; para <ID del contenedor>
, sustituya el valor de la RECIPIENTE
IDENTIFICACIÓN
campo en la salida del paso anterior (aquí, 20d0c83a8b86
). El archivo de registro muestra una serie de mensajes de éxito, porque agregamos el bucle while
True
al código de la aplicação . Presione Ctrl+c
para salir del comando.
registros de Docker -f <ID del contenedor>200 éxito apiKey1
200 éxito apiKey1
200 éxito apiKey1
200 éxito apiKey1
200 éxito apiKey1
200 éxito apiKey1
200 éxito apiKey1
...
^c
Sabemos que no se establecen variables de entorno sensibles (pero siempre puedes comprobarlo con el comando docker
inspect
como en el Paso 2 de Examinar el contenedor en el Desafío 2).
Del Desafío 3 también sabemos que el archivo /run/secrets/jot está vacío, pero puedes comprobarlo:
cd extractdocker export -o api3.tar
tar --extract --file=api3.tar ejecutar/secretos/jot
cat ejecutar/secretos/jot
¡Éxito! No puedes obtener el secreto del contenedor ni leerlo directamente desde el secreto de Docker.
Por supuesto, con los privilegios adecuados podemos crear un servicio y configurarlo para leer el secreto en el registro o establecerlo como una variable de entorno. Además, es posible que hayas notado que la comunicación entre nuestro cliente API y el servidor no está cifrada (texto sin formato).
Por lo tanto, la fuga de secretos aún es posible con casi cualquier sistema de gestión de secretos. Una forma de limitar la posibilidad de que se produzcan daños es rotar (reemplazar) los secretos periódicamente.
Con Docker Swarm, solo puedes eliminar y luego volver a crear secretos (Kubernetes permite la actualización dinámica de secretos). Tampoco puedes eliminar secretos asociados a servicios en ejecución.
Enumere los servicios en ejecución:
servicio docker ls ID NOMBRE MODO ... sl4mvv48vgjz secretstack_apiclient replicado ... ... REPLICAS IMAGEN PUERTOS... 1/1 apiclient:último
Eliminar el servicio secretstack_apiclient .
servicio docker rm secretstack_apiclient
Elimina el secreto y vuelve a crearlo con un nuevo token:
docker secret rm jot
docker secret create jot ./token2.jwt
Recrear el servicio:
Implementación de la pila Docker: archivo de composición. Docker-compose.secretmgr.yml. Secretstack
Busque el ID del contenedor para apiclient
(para obtener un ejemplo de salida, consulte el Paso 3 en Implementar el contenedor y verificar los registros ):
docker ps --format "tabla {{.ID}}\t{{.Nombres}}\t{{.Imagen}}\t{{.RunningFor}}\t{{.Estado}}"
Muestra el archivo de registro de Docker, que muestra una serie de mensajes de éxito. Para <ID del contenedor>
, sustituya el valor de la RECIPIENTE
IDENTIFICACIÓN
campo en la salida del paso anterior. Presione Ctrl+c
para salir del comando.
registros de Docker -f <ID del contenedor>200 éxitos apiKey2
200 éxitos apiKey2
200 éxitos apiKey2
200 éxitos apiKey2
200 éxitos apiKey2
...
^c
¿Ves el cambio de apiKey1
a apiKey2
? Has girado el secreto.
En este tutorial, el servidor API aún acepta ambos JWT, pero en un entorno de producción puedes dejar obsoletos los JWT más antiguos al requerir ciertos valores para los reclamos en el JWT o al verificar las fechas de vencimiento de los JWT.
Tenga en cuenta también que si está usando un sistema de secretos que permite actualizar su secreto, su código debe volver a leer el secreto con frecuencia para captar nuevos valores secretos.
Para limpiar los objetos que creaste en este tutorial:
Eliminar el servicio secretstack_apiclient .
servicio docker rm secretstack_apiclient
Eliminar el secreto.
docker secret rm jot
Abandona el enjambre (suponiendo que hayas creado un enjambre sólo para este tutorial).
Docker Swarm deja --force
Matar el contenedor apiserver que se está ejecutando.
docker ps -a | grep "apiserver" | awk {'imprimir $1'} |xargs docker kill
Elimine los contenedores no deseados enumerándolos y luego eliminándolos.
docker ps -a --format "tabla {{.ID}}\t{{.Nombres}}\t{{.Imagen}}\t{{.RunningFor}}\t{{.Estado}}"docker rm <ID del contenedor>
Elimine cualquier imagen de contenedor no deseada enumerándola y eliminándola.
lista de imágenes de Docker imagen de Docker RM <ID de imagen>
Puedes utilizar este blog para implementar el tutorial en tu propio entorno o probarlo en nuestro laboratorio basado en navegador ( regístrate aquí ). Para obtener más información sobre el tema de la exposición de servicios de Kubernetes, siga las otras actividades de la Unidad 2: Gestión de secretos de microservicios 101 .
Para obtener más información sobre la autenticación JWT de nivel de producción con NGINX Plus, consulte nuestra documentación y lea Autenticación de clientes API con JWT y NGINX Plus en nuestro blog.
"Esta publicación de blog puede hacer referencia a productos que ya no están disponibles o que ya no reciben soporte. Para obtener la información más actualizada sobre los productos y soluciones F5 NGINX disponibles, explore nuestra familia de productos NGINX . NGINX ahora es parte de F5. Todos los enlaces anteriores de NGINX.com redirigirán a contenido similar de NGINX en F5.com.