Este post é um dos quatro tutoriais que ajudam você a colocar em prática os conceitos do Microservices de março de 2023: Comece a fornecer microsserviços :
Muitos dos seus microsserviços precisam de segredos para operar com segurança. Exemplos de segredos incluem a chave privada para um certificado SSL/TLS, uma chave API para autenticação em outro serviço ou uma chave SSH para login remoto. O gerenciamento adequado de segredos exige limitar rigorosamente os contextos em que os segredos são usados apenas aos lugares onde eles precisam estar e impedir que os segredos sejam acessados, exceto quando necessário. Mas essa prática é frequentemente ignorada na correria do desenvolvimento de aplicativos. O resultado? O gerenciamento inadequado de segredos é uma causa comum de vazamento de informações e explorações.
Neste tutorial, mostramos como distribuir e usar com segurança um JSON Web Token (JWT) que um contêiner de cliente usa para acessar um serviço. Nos quatro desafios deste tutorial, você experimentará quatro métodos diferentes para gerenciar segredos, para aprender não apenas como gerenciar segredos corretamente em seus contêineres, mas também sobre métodos que são inadequados:
Embora este tutorial use um JWT como um segredo de exemplo, as técnicas se aplicam a qualquer coisa para contêineres que você precise manter em segredo, como credenciais de banco de dados, chaves privadas SSL e outras chaves de API.
O tutorial utiliza dois componentes principais de software:
GET
ao servidor APIAssista a este vídeo para uma demonstração do tutorial em ação.
A maneira mais fácil de fazer este tutorial é se registrar no Microservices March e usar o laboratório baseado em navegador fornecido. Esta postagem fornece instruções para executar o tutorial em seu próprio ambiente.
Para concluir o tutorial em seu próprio ambiente, você precisa:
nano
ou vim
curl
(já instalado na maioria dos sistemas)git
(já instalado na maioria dos sistemas)Notas:
‑p
para definir um valor diferente para o servidor de teste ao iniciá-lo com o comando docker
run
. Em seguida, inclua o :<número_da_porta>
sufixo em host local
no enrolar
comandos.~
) representa seu diretório inicial.Nesta seção, você clona o repositório do tutorial , inicia o servidor de autenticação e envia solicitações de teste com e sem um token.
No seu diretório inicial, crie o diretório microservices-march e clone o repositório GitHub nele. (Você também pode usar um nome de diretório diferente e adaptar as instruções adequadamente.) O repositório inclui arquivos de configuração e versões separadas do aplicativo cliente da API que usam métodos diferentes para obter segredos.
mkdir ~/microservices-marchcd ~/microservices-march
git clone https://github.com/microservices-march/auth.git
Exiba o segredo. É um JWT assinado, comumente usado para autenticar clientes de API em servidores.
gato ~/microservices-march/auth/apiclient/token1.jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2Nz UyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA"
Embora existam algumas maneiras de usar esse token para autenticação, neste tutorial o aplicativo cliente da API o passa para o servidor de autenticação usando a estrutura de autorização de token portador do OAuth 2.0 . Isso envolve prefixar o JWT com Autorização:
Portador
como neste exemplo:
"Autorização: Portador eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA"
Alterar para o diretório do servidor de autenticação:
cd apiserver
Crie a imagem do Docker para o servidor de autenticação (observe o ponto final):
docker build -t apiserver .
Inicie o servidor de autenticação e confirme se ele está em execução (a saída é distribuída em várias linhas para legibilidade):
docker run -d -p 80:80 apiserver docker ps COMANDO DE IMAGEM DE ID DO CONTAINER ...
2b001f77c5cb apiserver "nginx -g 'daemon de..." ... ... STATUS CRIADO ... ... 26 segundos atrás Up 26 segundos ... ... PORTOS ... ... 0.0.0.0:80->80/tcp, :::80->80/tcp, 443/tcp ... ... NOMES ... relaxado_proskuriakova
Verifique se o servidor de autenticação rejeita uma solicitação que não inclui o JWT, retornando 401
Autorização
necessária
:
curl -X OBTER http://localhost<html>
<head><title>Autorização 401 necessária</title></head>
<body>
<center><h1>Autorização 401 necessária</h1></center>
<hr><center>nginx/1.23.3</center>
</body>
</html>
Forneça o JWT usando o cabeçalho Authorization
. O200
O código de retorno OK
indica que o aplicativo cliente da API foi autenticado com sucesso.
curl -i -X GET -H "Autorização: Portador `cat $HOME/microservices-march/auth/apiclient/token1.jwt`" http://localhost HTTP/1.1 200 OK Servidor: nginx/1.23.2 Data: Dia , DD Seg AAAA hh : mm : ss TZ Tipo de conteúdo: text/html Comprimento do conteúdo: 64 Última modificação: Dia , DD Seg AAAA hh : mm : ss TZ Conexão: keep-alive ETag: "63dc0fcd-40" MENSAGEM X: Sucesso apiKey1 Accept-Ranges: bytes { "response": "success", "authorized": true, "value": "999" }
Antes de começar esse desafio, vamos deixar claro: codificar segredos no seu aplicativo é uma péssima ideia! Você verá como qualquer pessoa com acesso à imagem do contêiner pode facilmente encontrar e extrair credenciais codificadas.
Neste desafio, você copia o código do aplicativo cliente da API para o diretório de compilação, cria e executa o aplicativo e extrai o segredo .
O subdiretório app_versions do diretório apiclient contém diferentes versões do aplicativo cliente de API simples para os quatro desafios, cada uma ligeiramente mais segura que a anterior (consulte Visão geral do tutorial para obter mais informações).
Alterar para o diretório do cliente da API:
cd ~/microservices-março/auth/apiclient
Copie o aplicativo para este desafio – aquele com um segredo codificado – para o diretório de trabalho:
cp ./app_versions/código_rígido_muito_ruim.py ./app.py
Dê uma olhada no aplicativo:
cat app.py import urllib.request import urllib.error jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA" authstring = "Portador " + jwt req = urllib.request.Request("http://host.docker.internal") req.add_header("Autorização", authstring) try: with urllib.request.urlopen(req) como resposta: the_page = response.read() message = response.getheader("X-MESSAGE") print("200 " + message) except urllib.error.URLError as e: print(str(e.code) + " s " + e.msg)
O código simplesmente faz uma solicitação a um host local e imprime uma mensagem de sucesso ou um código de falha.
A solicitação adiciona o cabeçalho Authorization
nesta linha:
req.add_header("Autorização", authstring)
Você notou mais alguma coisa? Talvez um JWT codificado? Chegaremos a isso em um minuto. Primeiro, vamos construir e executar o aplicativo.
Estamos usando o comando docker
compose
junto com um arquivo YAML do Docker Compose – isso torna um pouco mais fácil entender o que está acontecendo.
(Observe que na Etapa 2 da seção anterior você renomeou o arquivo Python para o aplicativo cliente da API específico do Desafio 1 ( very_bad_hard_code.py ) para app.py . Você também fará isso nos outros três desafios. Usar app.py sempre simplifica a logística porque você não precisa alterar o Dockerfile . Isso significa que você precisa incluir o argumento ‑build
no comando docker
compose
para forçar uma reconstrução do contêiner a cada vez.)
O comando docker
compose
cria o contêiner, inicia o aplicativo, faz uma única solicitação de API e, em seguida, desliga o contêiner, enquanto exibe os resultados da chamada de API no console.
O200
O código de sucesso
na penúltima linha da saída indica que a autenticação foi bem-sucedida. O valor apiKey1
é uma confirmação adicional, porque mostra que o servidor de autenticação foi capaz de decodificar a reivindicação desse nome no JWT:
docker compose -f docker-compose.hardcode.yml up -build ... apiclient-apiclient-1 | 200 Sucesso apiKey1 apiclient-apiclient-1 saiu com código 0
Portanto, as credenciais codificadas funcionaram corretamente para nosso aplicativo cliente de API – o que não é surpreendente. Mas é seguro? Talvez sim, já que o contêiner executa esse script apenas uma vez antes de sair e não tem um shell?
Na verdade, não, não é nada seguro.
A codificação rígida de credenciais as deixa abertas para inspeção por qualquer pessoa que possa acessar a imagem do contêiner, porque extrair o sistema de arquivos de um contêiner é um exercício trivial.
Crie o diretório de extração e altere para ele:
mkdir extraircd extrair
Liste informações básicas sobre as imagens do contêiner. O sinalizador --format
torna a saída mais legível (e a saída é distribuída em duas linhas aqui pelo mesmo motivo):
docker ps -a --format "tabela {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}" ID DO CONTAINER NOMES IMAGEM ...
11b73106fdf8 apiclient-apiclient-1 apiclient ... ad9bdc05b07c exciting_clarke apiserver ... ... STATUS CRIADO ... 6 minutos atrás Saiu (0) 4 minutos atrás ... 43 minutos atrás Up 43 minutos
Extraia a imagem apiclient mais recente como um arquivo .tar . Para <ID_do_container>
, substitua o valor do RECIPIENTE
EU IA
campo na saída acima (11b73106fdf8
neste tutorial):
docker export -o api.tar <ID_do_container>
Leva alguns segundos para criar o arquivo api.tar , que inclui todo o sistema de arquivos do contêiner. Uma abordagem para encontrar segredos é extrair o arquivo inteiro e analisá-lo, mas acontece que há um atalho para encontrar o que provavelmente é interessante: exibir o histórico do contêiner com o comando docker
history
. (Este atalho é especialmente útil porque também funciona para contêineres que você encontra no Docker Hub ou em outro registro de contêiner e, portanto, podem não ter o Dockerfile , mas apenas a imagem do contêiner).
Exibir o histórico do contêiner:
docker history apiclient IMAGEM CRIADA ...
9396dde2aad0 8 minutos atrás ... 8 minutos atrás ... 28 minutos atrás ... ... CRIADO POR TAMANHO ... ... CMD ["python" "./app.py"] 622B ... ... COPIAR ./app.py ./app.py # buildkit 0B ... ... WORKDIR /usr/app/src 0B ... ... COMENTÁRIO ... buildkit.dockerfile.v0 ... buildkit.dockerfile.v0 ... buildkit.dockerfile.v0
As linhas de saída estão em ordem cronológica inversa. Eles mostram que o diretório de trabalho foi definido como /usr/app/src e, em seguida, o arquivo de código Python para o aplicativo foi copiado e executado. Não é preciso ser um grande detetive para deduzir que o código-base principal deste contêiner está em /usr/app/src/app.py e, portanto, esse é um local provável para credenciais.
Armado com esse conhecimento, extraia apenas esse arquivo:
tar --extract --file=api.tar usr/app/src/app.py
Exiba o conteúdo do arquivo e, assim, teremos acesso ao JWT “seguro”:
cat usr/app/src/app.py ... jwt="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA" ...
Se você concluiu a Unidade 1 de Microsserviços de março de 2023 (Aplicar o aplicativo Twelve‑Factor às arquiteturas de microsserviços), está familiarizado com o uso de variáveis de ambiente para passar dados de configuração para contêineres. Se você perdeu, não se preocupe: ele estará disponível mediante solicitação após o registro .
Neste desafio, você passa segredos como variáveis de ambiente. Assim como o método do Desafio 1 , não recomendamos este! Não é tão ruim quanto codificar segredos, mas, como você verá, tem algumas fraquezas.
Há quatro maneiras de passar variáveis de ambiente para um contêiner:
Use a instrução ENV
em um Dockerfile para fazer a substituição de variáveis (definir a variável para todas as imagens criadas). Por exemplo:
PORTA ENV $PORTA
Use o sinalizador ‑e
no comando docker
run
. Por exemplo:
docker run -e SENHA=123 meucontainer
de ambiente
em um arquivo YAML do Docker Compose.Neste desafio, você usa uma variável de ambiente para definir o JWT e examinar o contêiner para ver se o JWT está exposto.
Retorne ao diretório do cliente da API:
cd ~/microservices-março/auth/apiclient
Copie o aplicativo para este desafio – aquele que usa variáveis de ambiente – para o diretório de trabalho, sobrescrevendo o arquivo app.py do Desafio 1:
cp ./app_versions/medium_environment_variables.py ./app.py
Dê uma olhada no aplicativo. Nas linhas relevantes de saída, o segredo (JWT) é lido como uma variável de ambiente no contêiner local:
cat app.py ... jwt = "" se "JWT" em os.environ: jwt = "Portador " + os.environ.get("JWT") ...
Conforme explicado acima, há várias maneiras de colocar a variável de ambiente no contêiner. Para manter a consistência, estamos usando o Docker Compose. Exiba o conteúdo do arquivo YAML do Docker Compose, que usa a chave environment
para definir a variável de ambiente JWT
:
cat docker-compose.env.yml --- versão: Serviços "3.9": apiclient: build: . image: apiclient extra_hosts: - "host.docker.internal:host-gateway" environment: - JWT
Execute o aplicativo sem definir a variável de ambiente. O401
Código não autorizado
na penúltima linha da saída confirma que a autenticação falhou porque o aplicativo cliente da API não passou no JWT:
docker compose -f docker-compose.env.yml up -build ... apiclient-apiclient-1 | 401 O apiclient-apiclient-1 não autorizado saiu com o código 0
Para simplificar, defina a variável de ambiente localmente. Não há problema em fazer isso neste ponto do tutorial, já que não é uma questão de segurança que está em questão agora:
exportar JWT=`cat token1.jwt`
Execute o contêiner novamente. Agora o teste foi bem-sucedido, com a mesma mensagem do Desafio 1:
docker compose -f docker-compose.env.yml up -build ... apiclient-apiclient-1 | 200 Sucesso apiKey1 apiclient-apiclient-1 saiu com o código 0
Então, pelo menos agora a imagem base não contém o segredo e podemos passá-lo em tempo de execução, o que é mais seguro. Mas ainda há um problema.
Exibir informações sobre as imagens do contêiner para obter o ID do contêiner para o aplicativo cliente da API (a saída é distribuída em duas linhas para legibilidade):
docker ps -a --format "tabela {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}" ID DO CONTAINER NOMES IMAGEM ...
6b20c75830df apiclient-apiclient-1 apiclient ... ad9bdc05b07c exciting_clarke apiserver ... ... STATUS CRIADO ... 6 minutos atrás Saiu (0) 6 minutos atrás ... Cerca de uma hora atrás Up Cerca de uma hora atrás
Inspecione o contêiner para o aplicativo cliente da API. Para <ID_do_container>
, substitua o valor do RECIPIENTE
EU IA
campo na saída acima (aqui 6b20c75830df
).
O comando docker
inspect
permite que você inspecione todos os contêineres iniciados, estejam eles em execução ou não. E esse é o problema: mesmo que o contêiner não esteja em execução, a saída expõe o JWT na matriz Env
, salvo de forma insegura na configuração do contêiner.
Inspeção do docker <ID_do_container>...
"Env": [ "JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA...", "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "LANG=C.UTF-8", "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D", "VERSÃO_PYTHON=3.11.2", "VERSÃO_PYTHON_PIP=22.3.1", "VERSÃO_PYTHON_SETUPTOOLS=65.5.1", "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/1a96dc5acd0303c4700e026...", "PYTHON_GET_PIP_SHA256=d1d09b0f9e745610657a528689ba3ea44a73bd19c60f4c954271b790c..." ]
Agora você já aprendeu que codificar segredos e usar variáveis de ambiente não é tão seguro quanto você (ou sua equipe de segurança) precisa.
Para melhorar a segurança, você pode tentar usar segredos locais do Docker para armazenar informações confidenciais. Novamente, este não é o método padrão ouro, mas é bom entender como ele funciona. Mesmo que você não use o Docker em produção, o importante é como você pode dificultar a extração do segredo de um contêiner.
No Docker, os segredos são expostos a um contêiner por meio da montagem do sistema de arquivos /run/secrets/, onde há um arquivo separado contendo o valor de cada segredo.
Neste desafio, você passa um segredo armazenado localmente para o contêiner usando o Docker Compose e, em seguida, verifica se o segredo não fica visível no contêiner quando esse método é usado.
Como você deve ter imaginado, comece mudando para o diretório apiclient :
cd ~/microservices-março/auth/apiclient
Copie o aplicativo para este desafio – aquele que usa segredos de dentro de um contêiner – para o diretório de trabalho, sobrescrevendo o arquivo app.py do Desafio 2:
cp ./app_versions/better_secrets.py ./app.py
Dê uma olhada no código Python, que lê o valor JWT do arquivo /run/secrets/jot . (E sim, provavelmente deveríamos verificar se o arquivo tem apenas uma linha. Talvez em Microservices em março de 2024?)
cat app.py ... jotfile = "/run/secrets/jot" jwt = "" if os.path.isfile(jotfile): with open(jotfile) as jwtfile: for line in jwtfile: jwt = "Bearer " + line ...
OK, então como vamos criar esse segredo? A resposta está no arquivo docker-compose.secrets.yml .
Dê uma olhada no arquivo Docker Compose, onde o arquivo secreto é definido na seção secrets
e então referenciado pelo serviço apiclient
:
cat docker-compose.secrets.yml --- versão: "3.9" segredos: jot: arquivo: token1.jwt serviços: apiclient: build: . extra_hosts: - "host.docker.internal:host-gateway" segredos: - jot
Execute o aplicativo. Como tornamos o JWT acessível dentro do contêiner, a autenticação é bem-sucedida com a mensagem agora familiar:
docker compose -f docker-compose.secrets.yml up -build ... apiclient-apiclient-1 | 200 Sucesso apiKey1 apiclient-apiclient-1 saiu com o código 0
Exibir informações sobre as imagens do contêiner, observando o ID do contêiner para o aplicativo cliente da API (para obter um exemplo de saída, consulte a Etapa 1 em Examinar o contêiner do Desafio 2):
docker ps -a --format "tabela {{.ID}}\t{{.Nomes}}\t{{.Imagem}}\t{{.RunningFor}}\t{{.Status}}"
Inspecione o contêiner para o aplicativo cliente da API. Para <ID_do_container>
, substitua o valor do RECIPIENTE
EU IA
campo na saída da etapa anterior. Diferentemente da saída na Etapa 2 de Examinar o contêiner , não há nenhuma linha JWT=
no início da seção Env
:
inspeção do docker <ID_do_container>
"Env": [
"PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=C.UTF-8",
"GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
"VERSÃO_PYTHON=3.11.2",
"VERSÃO_PYTHON_PIP=22.3.1",
"VERSÃO_PYTHON_SETUPTOOLS=65.5.1",
"URL_PYTHON_GET_PIP=https://github.com/pypa/get-pip/raw/1a96dc5acd0303c4700e026...",
"PYTHON_GET_PIP_SHA256=d1d09b0f9e745610657a528689ba3ea44a73bd19c60f4c954271b790c..."
]
Até agora, tudo bem, mas nosso segredo está no sistema de arquivos do contêiner em /run/secrets/jot . Talvez possamos extraí-lo de lá usando o mesmo método usado em Recuperar o Segredo da Imagem do Contêiner do Desafio 1.
Mude para o diretório de extração (que você criou durante o Desafio 1) e exporte o contêiner para um arquivo tar :
cd extractdocker export -o api2.tar <ID_do_container>
Procure o arquivo secrets no arquivo tar :
tar tvf api2.tar | grep jot -rwxr-xr-x 0 0 0 0 Mon DD hh :mm run/secrets/jot
Opa, o arquivo com o JWT está visível. Não dissemos que incorporar segredos no contêiner era “seguro”? As coisas estão tão ruins quanto no Desafio 1?
Vamos ver – extraia o arquivo secrets do arquivo tar e observe seu conteúdo:
tar --extract --file=api2.tar executar/segredos/jotcat executar/segredos/jot
Boas notícias! Não há saída do comando cat
, o que significa que o arquivo run/secrets/jot no sistema de arquivos do contêiner está vazio – não há nenhum segredo para ver lá! Mesmo que haja um artefato secreto em nosso contêiner, o Docker é inteligente o suficiente para não armazenar nenhum dado confidencial no contêiner.
Dito isto, embora essa configuração de contêiner seja segura, ela tem uma deficiência. Depende da existência de um arquivo chamado token1.jwt no sistema de arquivos local quando você executa o contêiner. Se você renomear o arquivo, a tentativa de reiniciar o contêiner falhará. (Você pode tentar fazer isso renomeando [não excluindo!] token1.jwt e executando o comando docker
compose
da Etapa 1 novamente.)
Então estamos na metade do caminho: o contêiner usa segredos de uma forma que os protege de comprometimento fácil, mas o segredo ainda está desprotegido no host. Você não quer segredos armazenados sem criptografia em um arquivo de texto simples. É hora de trazer uma ferramenta de gerenciamento de segredos.
Um gerenciador de segredos ajuda você a gerenciar, recuperar e rotacionar segredos ao longo de seus ciclos de vida. Há muitos gerenciadores de segredos para escolher e todos eles cumprem uma finalidade semelhante:
Suas opções para gerenciamento de segredos incluem:
Para simplificar, este desafio usa o Docker Swarm, mas os princípios são os mesmos para muitos gerenciadores de segredos.
Neste desafio, você cria um segredo no Docker , copia o segredo e o código do cliente da API , implanta o contêiner , vê se consegue extrair o segredo e rotaciona o segredo .
Como já é tradição, vá para o diretório apiclient :
cd ~/microservices-março/auth/apiclient
Inicializar Docker Swarm:
docker swarm init Swarm inicializado: o nó atual (t0o4eix09qpxf4ma1rrs9omrm) agora é um gerenciador. ...
Crie um segredo e armazene-o em token1.jwt :
docker segredo criar jot ./token1.jwt qe26h73nhb35bak5fr5east27
Exibir informações sobre o segredo. Observe que o valor secreto (o JWT) não é exibido:
docker secret inspect jot [ { "ID": "qe26h73nhb35bak5fr5east27", "Versão": { "Índice": 11 }, "CriadoEm": " AAAA - MM - DD T hh : mm : ss . ms Z", "AtualizadoEm": " AAAA - MM - DD T hh : mm : ss . ms Z", "Especificação": { "Nome": "jot", "Rótulos": {} } } ]
Usar o segredo do Docker no código do aplicativo cliente da API é exatamente o mesmo que usar um segredo criado localmente – você pode lê-lo no sistema de arquivos /run/secrets/ . Tudo o que você precisa fazer é alterar o qualificador secreto no seu arquivo YAML do Docker Compose.
Dê uma olhada no arquivo YAML do Docker Compose. Observe o valor true
no campo externo
, indicando que estamos usando um segredo do Docker Swarm:
cat docker-compose.secretmgr.yml --- versão: "3.9" segredos: jot: externo: verdadeiro serviços: apiclient: construção: . imagem: apiclient extra_hosts: - "host.docker.internal:host-gateway" segredos: - jot
Portanto, podemos esperar que este arquivo Compose funcione com nosso código de aplicativo cliente de API existente. Bem, quase. Embora o Docker Swarm (ou qualquer outra plataforma de orquestração de contêineres) traga muito valor extra, ele traz alguma complexidade adicional.
Como o docker
compose
não funciona com segredos externos, teremos que usar alguns comandos do Docker Swarm, especialmente o docker
stack
deploy
. O Docker Stack oculta a saída do console, então temos que gravar a saída em um log e depois inspecionar o log.
Para facilitar as coisas, também usamos um loop while
True
contínuo para manter o contêiner em execução.
Copie o aplicativo para este desafio – aquele que usa um gerenciador de segredos – para o diretório de trabalho, sobrescrevendo o arquivo app.py do Desafio 3. Exibindo o conteúdo de app.py , vemos que o código é quase idêntico ao código do Desafio 3. A única diferença é a adição do loop while
True
:
cp ./app_versions/best_secretmgr.py ./app.pycat ./app.py ... while True: time.sleep(5) try: with urllib.request.urlopen(req) as response: the_page = response.read() message = response.getheader("X-MESSAGE") print("200 " + message, file=sys.stderr) except urllib.error.URLError as e: print(str(e.code) + " " + e.msg, file=sys.stderr)
Crie o contêiner (em desafios anteriores, o Docker Compose cuidou disso):
docker build -t apiclient .
Implante o contêiner:
docker stack deploy --compose-file docker-compose.secretmgr.yml secretstack Criando rede secretstack_default Criando serviço secretstack_apiclient
Liste os contêineres em execução, anotando o ID do contêiner para secretstack_apiclient (como antes, a saída é distribuída em várias linhas para facilitar a leitura).
docker ps --format "tabela {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}" ID DO CONTAINER ...
20d0c83a8b86 ... ad9bdc05b07c ... ... NOMES ... ... secretstack_apiclient.1.0e9s4mag5tadvxs6op6lk8vmo ... ... exciting_clarke ... ... STATUS DA IMAGEM CRIADA ... apiclient:latest 31 segundos atrás Up 30 segundos ... apiserver 2 horas atrás Up 2 horas
Exibir o arquivo de log do Docker; para <ID_do_container>
, substitua o valor do RECIPIENTE
EU IA
campo na saída da etapa anterior (aqui, 20d0c83a8b86
). O arquivo de log mostra uma série de mensagens de sucesso, porque adicionamos o loop while
True
ao código do aplicativo. Pressione Ctrl+c
para sair do comando.
registros do docker -f <ID_do_container>200 Sucesso apiKey1
200 Sucesso apiKey1
200 Sucesso apiKey1
200 Sucesso apiKey1
200 Sucesso apiKey1
200 Sucesso apiKey1
200 Sucesso apiKey1
...
^c
Sabemos que nenhuma variável de ambiente sensível está definida (mas você sempre pode verificar com o comando docker
inspect,
como na Etapa 2 de Examinar o contêiner no Desafio 2).
Do Desafio 3 também sabemos que o arquivo /run/secrets/jot está vazio, mas você pode verificar:
cd extractdocker export -o api3.tar
tar --extract --file=api3.tar executar/segredos/jot
cat executar/segredos/jot
Sucesso! Você não pode obter o segredo do contêiner, nem lê-lo diretamente do segredo do Docker.
É claro que, com os privilégios certos, podemos criar um serviço e configurá-lo para ler o segredo no log ou defini-lo como uma variável de ambiente. Além disso, você deve ter notado que a comunicação entre nosso cliente de API e o servidor não é criptografada (texto simples).
Portanto, o vazamento de segredos ainda é possível com quase qualquer sistema de gerenciamento de segredos. Uma maneira de limitar a possibilidade de danos resultantes é rotacionar (substituir) os segredos regularmente.
Com o Docker Swarm, você só pode excluir e recriar segredos (o Kubernetes permite atualização dinâmica de segredos). Você também não pode excluir segredos anexados a serviços em execução.
Listar os serviços em execução:
serviço docker ls ID NOME MODO ... sl4mvv48vgjz secretstack_apiclient replicado ... ... PORTAS DE IMAGEM DE RÉPLICAS ... 1/1 apiclient:mais recente
Exclua o serviço secretstack_apiclient .
serviço docker rm secretstack_apiclient
Exclua o segredo e recrie-o com um novo token:
segredo do docker rm jot
segredo do docker criar jot ./token2.jwt
Recrie o serviço:
pilha docker implantar --compose-file docker-compose.secretmgr.yml pilha secreta
Procure o ID do contêiner para apiclient
(para obter um exemplo de saída, consulte a Etapa 3 em Implantar o contêiner e verificar os logs ):
docker ps --format "tabela {{.ID}}\t{{.Nomes}}\t{{.Imagem}}\t{{.RunningFor}}\t{{.Status}}"
Exiba o arquivo de log do Docker, que mostra uma série de mensagens de sucesso. Para <ID_do_container>
, substitua o valor do RECIPIENTE
EU IA
campo na saída da etapa anterior. Pressione Ctrl+c
para sair do comando.
registros do docker -f <ID_do_container>200 Sucesso apiKey2
200 Sucesso apiKey2
200 Sucesso apiKey2
200 Sucesso apiKey2
...
^c
Viu a mudança de apiKey1
para apiKey2
? Você girou o segredo.
Neste tutorial, o servidor de API ainda aceita ambos os JWTs, mas em um ambiente de produção você pode descontinuar JWTs mais antigos exigindo determinados valores para declarações no JWT ou verificando as datas de expiração dos JWTs.
Observe também que se você estiver usando um sistema de segredos que permite que seu segredo seja atualizado, seu código precisa reler o segredo com frequência para obter novos valores secretos.
Para limpar os objetos que você criou neste tutorial:
Exclua o serviço secretstack_apiclient .
serviço docker rm secretstack_apiclient
Apague o segredo.
docker segredo rm jot
Deixe o enxame (supondo que você criou um enxame apenas para este tutorial).
docker swarm sair --força
Mate o contêiner apiserver em execução.
docker ps -a | grep "apiserver" | awk {'imprimir $1'} |xargs docker kill
Exclua contêineres indesejados listando-os e depois excluindo-os.
docker ps -a --format "tabela {{.ID}}\t{{.Nomes}}\t{{.Imagem}}\t{{.RunningFor}}\t{{.Status}}"docker rm <ID_do_container>
Exclua quaisquer imagens de contêiner indesejadas listando-as e excluindo-as.
lista de imagens docker imagem docker rm <ID_da_imagem>
Você pode usar este blog para implementar o tutorial em seu próprio ambiente ou experimentá-lo em nosso laboratório baseado em navegador ( registre-se aqui ). Para saber mais sobre o tópico de exposição de serviços do Kubernetes, acompanhe as outras atividades da Unidade 2: Segredos básicos de gerenciamento de microsserviços .
Para saber mais sobre autenticação JWT de nível de produção com NGINX Plus, confira nossa documentação e leia Autenticação de clientes de API com JWT e NGINX Plus em nosso blog.
"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."