Skip to main content

Boas práticas para conteinerizar aplicações Python com Docker

Escrito por

Daniel Campos Olivares

blog feature snyk python security

11 de novembro de 2021

0 minutos de leitura

Ao ler vários artigos sobre contêineres Docker para Python, percebemos que a maioria apresenta exemplos de como conteinerizar uma aplicação Python sem considerar o framework usado (Django, Flask, Falcon etc.). Por exemplo, você pode encontrar algo assim:

FROM python
WORKDIR /usr/app
COPY . .
RUN pip install -r requirements.txt
CMD [ "python", "app.py" ]

Com este Dockerfile, podemos criar e executar uma aplicação Python com Flask:

docker build -t flask-application .
docker run -p 8080:5000 flask-application

São apenas duas etapas e funciona direitinho, certo?

Embora este exemplo seja simples e útil para demonstrações e tutoriais para iniciantes, ele deixa de lado várias questões importantes. Pensando nisso, neste post vamos abordar essas questões e conferir 6 boas práticas para conteinerizar aplicações Python com Docker. Vamos explorar por que você deve:

  1. Usar tags explícitas e determinísticas para as imagens-base do Docker em aplicações Python conteinerizadas.

  2. Separar as dependências do código-fonte.

  3. Usar Python WSGI em produção.

  4. Executar contêineres com o mínimo de privilégios possível (e nunca como root).

  5. Lidar com estados não saudáveis da aplicação.

  6. Encontrar e corrigir vulnerabilidades de segurança na imagem Docker da sua aplicação Python.

1. Use tags explícitas e determinísticas para as imagens-base do Docker em aplicações Python conteinerizadas

Embora pareça lógico usar python como imagem-base da nossa aplicação Python no Docker, isso deixa em aberto qual versão do Python está sendo usada.

Na época em que este artigo foi escrito, a imagem-base mencionada no Dockerfile correspondia a uma imagem com Python 3.10. Por quê? Como não adicionamos uma tag específica, o padrão foi a versão :latest dessa imagem-base, que, como podemos ver na página oficial da imagem no Docker Hub, é a 3.10

Como queremos controlar as versões do Python que vamos conteinerizar, devemos sempre especificar essa informação no Dockerfile.

Então, como quero usar a versão 3.10 do Python, basta adicionar a tag :3.10 ao meu Dockerfile, certo?

Bem... não exatamente.

A tag da imagem-base do Docker :3.10 corresponde a um sistema operacional completo com Python 3.10 instalado e inclui muitas bibliotecas que provavelmente você nunca vai usar. Além disso, essa grande quantidade de software tem um efeito colateral: aumenta a superfície de ataque devido às vulnerabilidades de segurança presentes nessas bibliotecas.

Se você usar uma imagem Docker grande para Python, será mais difícil fazer a manutenção e manter todas essas versões de bibliotecas atualizadas.

Se usarmos a ferramenta Snyk Advisor para analisar uma imagem-base do Python, veremos que a imagem-base do Docker python:3.10 tem 12 problemas de gravidade alta, 27 de gravidade média e 132 de gravidade baixa. Ou seja, por padrão, a imagem Docker do Python já começa com pelo menos 171 vulnerabilidades de segurança — e ainda não adicionamos nada!

Além disso, como estamos incluindo um sistema operacional completo, a imagem-base será bem grande para nosso servidor de aplicações Python. Isso resultará em builds mais lentos e exigirá mais espaço. Em geral, há algumas regras para escolher a imagem-base, mas duas são fundamentais.

Boas práticas para escolher uma imagem Docker para Python

  1. Escolha a menor imagem-base que atenda a todos os seus requisitos e construa a partir dela. Imagens menores contêm menos vulnerabilidades, consomem menos recursos e incluem menos pacotes desnecessários.

  2. Usar uma tag nomeada não basta para garantir que você sempre usará a mesma imagem-base. A única forma de garantir isso é usar o digest da imagem.

Com isso em mente, vamos voltar ao Snyk Advisor e conferir quais tags alternativas ele recomenda. A ferramenta oferece uma ótima visão geral das vulnerabilidades e do tamanho das imagens-base, o que ajuda muito na hora de decidir.

Como queremos usar o Python 3.10 na nossa aplicação, vamos procurar essa tag específica.

Página do Snyk Advisor sobre a imagem oficial do Python para Docker, com o comando docker pull e recomendações de tags alternativas com dados de gravidade, atualização e tamanho

Além das vulnerabilidades mencionadas, podemos ver que esta imagem tem cerca de 350 MB, é baseada no Debian 11 e tem 427 pacotes instalados. Consideramos que ela é grande demais para a nossa pequena aplicação Python.

Por outro lado, a imagem-base do Docker para Python :3.10-slim tem 1 problema de gravidade alta, 1 de gravidade média e 35 de gravidade baixa. Seu tamanho-base é de 46,2 MB, ela também é baseada no Debian 11 e tem 106 pacotes instalados. Ao escolher essa imagem-base do Docker para nosso servidor de aplicações Python em vez da padrão, reduzimos o número de vulnerabilidades de segurança, o espaço em disco e a quantidade de bibliotecas instaladas — além de atender ao nosso requisito de usar a versão :3.10 do Python.

Pronto! É só adicionar a tag :3.10-slim e seguir em frente!

Quase! Cumprimos a primeira regra — temos uma imagem-base pequena do Docker que atende aos nossos requisitos —, mas ainda precisamos resolver a segunda questão. Precisamos garantir que usaremos exatamente essa imagem-base do Docker sempre que criarmos nosso servidor de aplicações Python.

Para fazer isso, temos algumas opções:

  1. Obter o digest da imagem-base do Docker no Docker Hub.

  2. Baixar a imagem Docker para o nosso computador com docker pull python:3.10-slim, o que revela o digest da imagem:

3.10-slim: Pulling from library/python
7d63c13d9b9b: Pull complete
6ad2a11ca37b: Pull complete
1d79bc863ed3: Pull complete
c72b5f03bec8: Pull complete
0c3b0c5ce69b: Pull complete
Digest: sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
Status: Downloaded newer image for python:3.10-slim
docker.io/library/python:3.10-slim

Se já tivermos a imagem Docker do Python no computador, basta obter o digest da imagem existente em disco com o comando docker images --digests | grep python:

python    3.10-slim    sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d

Depois de obter o digest da imagem-base, basta adicioná-lo ao Dockerfile mencionado acima:

Dockerfile

FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
WORKDIR /usr/app
COPY . .
RUN pip install -r requirements.txt
CMD [ "python", "app.py" ]

Essa prática garante que, sempre que recriarmos a imagem Docker desta aplicação Python, usaremos as mesmas versões do sistema operacional e das bibliotecas subjacentes. Isso torna o build determinístico.

2. Separe as dependências do código-fonte

Esta segunda boa prática evita um dos erros mais comuns em qualquer tipo de imagem Docker que envolva projetos com dependências. Primeiro, veja o que não fazer:

  1. Copiar tudo da pasta do projeto para o contexto da imagem.

  2. Instalar as dependências.

  3. Executar a aplicação.

Funciona, mas há muito espaço para melhorar. Para começar, quando você desenvolve um projeto localmente, só instala as dependências quando elas mudam, certo? Então, não faça a imagem Docker baixar e instalar tudo de novo sempre que você alterar uma linha de código sequer.

Esta boa prática trata de otimizar as camadas de uma imagem Docker. Para aproveitar o sistema de cache durante um build do Docker, devemos ter sempre uma coisa em mente ao escrever um Dockerfile: ordenar as camadas de acordo com a frequência com que podem mudar.

Vamos conferir o Dockerfile que usamos até agora. Sempre que criamos a imagem da aplicação Python, o Docker examina as diferentes camadas e responde à pergunta: Algo mudou ou posso simplesmente reutilizar o que já fiz?

Na versão atual do Dockerfile, qualquer alteração na pasta do projeto aciona novamente a instrução COPY — e, em seguida, todas as camadas restantes do build. Isso não faz muito sentido e deixa bastante espaço para otimizar e acelerar o processo.

Vamos melhorar isso com o seguinte Dockerfile:

FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
WORKDIR /usr/app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .
CMD [ "python", "app.py" ]

Com este novo Dockerfile, na próxima vez que o Docker verificar se pode reutilizar as camadas, se não houver alterações no arquivo requirements.txt, ele irá direto para a instrução COPY, que será processada em questão de segundos. Essa pequena mudança acelera bastante o processo de build — chega de esperar minutos entre builds toda vez que alteramos algo no código.

Vale lembrar que nem todas as dependências são necessariamente distribuídas como wheels. Nesse caso, seria preciso instalar um compilador na imagem.

Mas você não disse para usar a menor imagem possível para executar a aplicação?

Você tem razão. Por isso, vamos mostrar outro recurso incrível do Docker: os builds em vários estágios.

Builds em vários estágios

Em um build de vários estágios, usamos uma imagem Docker com mais ferramentas para compilar as dependências necessárias e, depois, copiamos apenas os artefatos necessários para a imagem Docker do Python usada na aplicação.

Veja um exemplo com aplicações baseadas em Node.js:

FROM node:latest AS build
ARG NPM_TOKEN
WORKDIR /usr/src/app
COPY package*.json /usr/src/app/
RUN npm install

FROM node:lts-alpine@sha256:b2da3316acdc2bec442190a1fe10dc094e7ba4121d029cb32075ff59bb27390a
WORKDIR /usr/src/app
COPY --from=build /usr/src/app/node_modules /usr/src/app/node_modules
COPY . .
CMD ["node", "server.js"]

Observação: Este é um exemplo breve, apenas para demonstrar os recursos dos builds em vários estágios. Se você quiser aprender as boas práticas para conteinerizar aplicações Node.js corretamente, confira este artigo de Liran Tal (esse nome parece familiar...) e Yoni Goldberg.

Fazer builds em vários estágios para Node.js é bem simples, porque a pasta node_modules fica no mesmo diretório do projeto. Mas esse não é o caso das aplicações Python.

Se simplesmente executarmos pip install, muitos itens serão instalados em vários locais, o que impossibilita fazer um build em vários estágios. Para resolver isso, temos duas opções:

  1. Usar pip install --user

  2. Usar um virtualenv

Usar pip install --user pode parecer uma boa opção, já que todos os pacotes serão instalados no diretório ~/.local e é fácil copiá-los de um estágio para outro. Mas isso cria outro problema: você adicionaria à imagem-base final do Docker todas as dependências de sistema da imagem usada para compilar as dependências — e não queremos isso (lembre-se da boa prática de usar a menor imagem-base possível).

Descartada a primeira opção, vamos explorar a segunda: usar um virtualenv. Nesse caso, o Dockerfile ficaria assim.

FROM python:3.10-slim as build
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
	      build-essential gcc 

WORKDIR /usr/app
RUN python -m venv /usr/app/venv
ENV PATH="/usr/app/venv/bin:$PATH"

COPY requirements.txt .
RUN pip install -r requirements.txt

FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
WORKDIR /usr/app/venv
COPY --from=build /usr/app/venv ./venv
COPY . .

ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "python", "app.py" ]

Agora temos todas as dependências necessárias, sem os pacotes extras exigidos para compilá-las.

Problemas conhecidos dos builds em vários estágios para aplicações Python conteinerizadas

Atualmente, há um problema conhecido: os estágios anteriores não são armazenados em cache. A maneira mais fácil de resolver isso é usar o BuildKit e adicionar o argumento BUILDKIT_INLINE_CACHE=1 ao processo de build.

Na primeira vez, faremos o build normalmente. Nas seguintes, usaremos o comando abaixo:

export DOCKER_BUILDKIT=1
docker build -t flask-application --cache-from flask-application --build-arg BUILDKIT_INLINE_CACHE=1 .

Você precisará da variável de ambiente DOCKER_BUILDKIT=1 para que o Docker saiba que deve usar o BuildKit no processo de build. Também é possível ativá-lo adicionando as configurações abaixo ao arquivo de configuração do Docker em /etc/docker/daemon.json (assim, não será necessário definir a variável de ambiente):

{ "features": { "buildkit": true } }

3. Use Python WSGI em produção

Manter o modo de depuração ativado em aplicações Python preparadas para produção é uma péssima ideia — e uma brecha de segurança esperando para ser explorada. Infelizmente, é um erro comum que encontramos em muitos artigos sobre aplicações Python Flask conteinerizadas e outros frameworks de aplicações Python WSGI.

O depurador permite executar código Python arbitrário pelo navegador, o que representa um enorme risco de segurança. É possível reduzir esse risco até certo ponto, mas ele sempre será uma vulnerabilidade. Mais uma vez, por segurança: não execute servidores de desenvolvimento nem depuradores em um ambiente de produção! Isso também vale para suas aplicações Python conteinerizadas.

Sabemos que é mais fácil simplesmente implantar suas aplicações Python Flask no modo de depuração do que configurar um servidor WSGI e o servidor web/proxy, mas isso só é mais fácil até você ter que explicar por que sua aplicação foi invadida.

Como resolver isso? Primeiro, você deve decidir qual implementação de servidor WSGI quer usar. Estas são as quatro opções mais comuns para aplicações Python:

  • Green Unicorn (Gunicorn) — Modelo de workers pré-fork, portado do projeto Unicorn, do Ruby.

  • uWSGI — Implementação versátil e eficiente de servidor WSGI, com baixo consumo de recursos.

  • mod_wsgi — Um módulo do Apache que pode hospedar qualquer aplicação web em Python compatível com a especificação WSGI do Python.

  • CherryPy — Um framework HTTP orientado a objetos, com estilo Python, que também funciona como servidor WSGI.

Neste artigo, usaremos o Gunicorn como exemplo, mas fique à vontade para consultar a documentação e as informações de cada opção e escolher a que melhor atende às suas necessidades. Nesta publicação, não abordaremos a configuração, pois ela depende totalmente do caso de uso.

Para a parte de containerização, precisamos apenas:

  1. Incluir a dependência gunicorn em requirements.txt

  2. Alterar o ponto de entrada do container da aplicação Python (consulte a instrução CMD para fazer essa alteração):

FROM python:3.10-slim as build
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
	build-essential gcc 

WORKDIR /usr/app
RUN python -m venv /usr/app/venv
ENV PATH="/usr/app/venv/bin:$PATH"

COPY requirements.txt .
RUN pip install -r requirements.txt

FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d
WORKDIR /usr/app
COPY --from=build /usr/app/venv ./venv
COPY . .

ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "manage:app" ]

Depois de recriar a aplicação Python com base nesse novo Dockerfile, podemos executá-la e testar se a aplicação Flask está pronta para processar solicitações:

docker run -p 8080:5000 flask-application

Observação: para implantações realmente prontas para produção, recomendamos não vincular diretamente ao host a porta exposta pelo Gunicorn. Em vez disso, recomendamos implantar um servidor proxy reverso na mesma rede, responsável por gerenciar todas as solicitações HTTP e servir arquivos estáticos.

4. Execute aplicações Python em containers com o menor privilégio possível (e nunca como root)

O princípio do menor privilégio é um controle de segurança usado há muito tempo, desde os primórdios do Unix — e devemos sempre segui-lo ao executar aplicações Python em containers.

A imagem oficial do Docker python não inclui um usuário privilegiado por padrão. Portanto, precisamos criá-lo para executar o processo com um usuário com o menor privilégio possível.

Para isso, adicionaremos os seguintes comandos groupadd ao Dockerfile da imagem final (a segunda etapa da compilação em várias etapas), que executa o processo gunicorn:

FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d

RUN groupadd -g 999 python && \
    useradd -r -u 999 -g python python
USER 999
WORKDIR /usr/app

COPY --from=build /usr/app/venv ./venv
COPY . .

ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "manage:app" ]

O problema é que, com as modificações anteriores, o usuário python é o proprietário do processo do sistema executado pelo Docker... E quanto à propriedade dos arquivos copiados ou do diretório WORKDIR? Por padrão, o compilador do Docker cria o diretório WORKDIR se ele não existir, mas o cria com o usuário do sistema root como proprietário. Assim, qualquer operação que envolva gravar nesse diretório pode causar uma falha fatal na aplicação. Além disso, por padrão, os arquivos copiados serão de propriedade de root se não alterarmos esse comportamento, mesmo que já tenhamos trocado de usuário.

Vamos corrigir isso:

FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d

RUN groupadd -g 999 python && \
    useradd -r -u 999 -g python python

RUN mkdir /usr/app && chown python:python /usr/app
WORKDIR /usr/app

COPY --chown=python:python --from=build /usr/app/venv ./venv
COPY --chown=python:python . .

USER 999

ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "manage:app" ]

Com as instruções COPY atualizadas acima, evitamos comportamentos inesperados relacionados à propriedade dos arquivos no diretório WORKDIR e garantimos que todos os arquivos pertençam ao mesmo usuário que executará o processo.

5. Gerencie estados não saudáveis da sua aplicação Python em container

Ao implantar uma aplicação, você precisa considerar possíveis eventos ou problemas não tratados que podem deixá-la em um estado não saudável, no qual: 1) ela para de funcionar, mas 2) o processo não é encerrado. Se isso acontecer, o container não será notificado, e você ficará com um servidor de aplicação Python em execução que não responde mais às solicitações HTTP.

Para evitar isso, queremos implementar um endpoint de verificação de integridade. Para verificar a integridade da sua aplicação Python em container, recomendamos sempre incluir um endpoint HTTP de monitoring ou de health, para confirmar que a aplicação continua processando as solicitações dos usuários corretamente. Em alguns frameworks web para Python, como o Flask, essa tarefa é simples (veja o exemplo abaixo). Em conjunto com a instrução HEALTHCHECK do Docker, ela garante que o estado da aplicação seja monitorado adequadamente.

Este trecho de uma aplicação Flask em Python adiciona um endpoint HTTP /health:

@app.route('/health', methods=['GET'])
def health():
	# Handle here any business logic for ensuring you're application is healthy (DB connections, etc...)
    return "Healthy: OK"

"Depois de adicionar esse endpoint à aplicação, basta incluir a instrução HEALTHCHECK no Dockerfile:

FROM python:3.10-slim@sha256:2bac43769ace90ebd3ad83e5392295e25dfc58e58543d3ab326c3330b505283d

RUN groupadd -g 999 python && \
    useradd -r -u 999 -g python python

RUN mkdir /usr/app && chown python:python /usr/app
WORKDIR /usr/app

COPY --chown=python:python --from=build /usr/app/venv ./venv
COPY --chown=python:python . .

USER 999

ENV PATH="/usr/app/venv/bin:$PATH"
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "manage:app" ]
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD curl -f http://localhost:5000/health

Se você estiver implantando no Kubernetes, saiba que as diretivas HEALTHCHECK do Dockerfile são ignoradas. É necessário incluir no YAML uma sonda de atividade, prontidão e inicialização do Kubernetes equivalente.

Então, para adaptar a instrução HEALTHCHECK acima às implantações no Kubernetes:

...
   livenessProbe:
     httpGet:
       path: /health
       port: 5000
     initialDelaySeconds: 5
     periodSeconds: 30
     timeoutSeconds: 30
     failureThreshold: 3
...

Essa configuração deve ficar aninhada na parte container spec do arquivo YAML do pod ou da implantação. Agora, com uma política de reinicialização definida como always ou unless_stopped, o container da nossa aplicação Python será sempre reiniciado se entrar em um estado não saudável.

6. Encontre e corrija vulnerabilidades de segurança na imagem Docker da sua aplicação Python

Já vimos que imagens-base maiores trazem vários desafios, como uma pilha de software extensa que precisamos manter atualizada com as correções de segurança, entre outras coisas.

Também vimos como usar o Snyk Advisor para consultar métricas de tamanho e vulnerabilidades de diferentes imagens-base. Mas o Advisor é só a ponta do iceberg. A Snyk é uma plataforma gratuita de segurança para desenvolvedores que você pode usar para testar tudo: do seu próprio código Python às dependências Python (como as listadas em requirements.txt), à imagem de container Python que executa sua aplicação e até às configurações de Terraform ou Kubernetes que orquestram tudo.

O melhor da Snyk é que ela recomenda correções para as vulnerabilidades encontradas. Em vez de apenas mostrar quais vulnerabilidades existem, ela cria automaticamente um pull request com a correção ou fornece orientações para remediá-las. E tudo isso acontece nas ferramentas (IDE, CLI, Docker etc.) e nos fluxos de trabalho (Git, CI/CD etc.) que você já usa.

Exemplo: como analisar com a Snyk nosso aplicativo Python em container

Vamos ver como isso funciona com a CLI da Snyk, usando a imagem Docker python:3.8 para compilar este exemplo de aplicação Flask em Python.

Se você instalar a CLI da Snyk, poderá usá-la para analisar as dependências do seu projeto Python, seu código Python e muito mais. Primeiro, vamos instalar a CLI da Snyk. Se você tiver um ambiente Node.js, poderá fazer isso com o gerenciador de pacotes npm, da seguinte forma:

npm install -g snyk

Se você usa macOS ou Linux e o Homebrew, também pode instalá-la assim:

brew tap snyk/tap
brew install snyk

Para conhecer outros métodos de instalação, consulte nosso guia sobre como instalar a CLI da Snyk.

Em seguida, precisamos nos autenticar pela CLI para obter um token de API válido e consultar o banco de dados de vulnerabilidades:

snyk auth

Com isso concluído, podemos criar uma imagem Docker local da aplicação Python usando a imagem-base python:3.8:

❯ docker build . -t python-flask-app
FROM python:3.8 as build
[+] Building 5.2s (8/13)
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 513B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/python:3.8
 => [auth] library/python:pull token for registry-1.docker.io
 => [internal] load build context
...

Agora, vamos analisá-la com a Snyk executando:

snyk container test python-flask-app

O resultado apresenta os dados a seguir (reduzidos intencionalmente, pois a saída é extensa):

Testing python-flask-app...

✗ Low severity vulnerability found in tiff/libtiff5
  Description: Out-of-bounds Read
  Info: https://snyk.io/vuln/SNYK-DEBIAN11-TIFF-514595
  Introduced through: imagemagick@8:6.9.11.60+dfsg-1.3, imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3
  From: imagemagick@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6.q16@8:6.9.11.60+dfsg-1.3 > imagemagick/libmagickcore-6.q16-6@8:6.9.11.60+dfsg-1.3 > tiff/libtiff5@4.2.0-1
  From: imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3 > imagemagick/libmagickcore-6.q16-dev@8:6.9.11.60+dfsg-1.3 > tiff/libtiff-dev@4.2.0-1 > tiff/libtiff5@4.2.0-1
  From: imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3 > imagemagick/libmagickcore-6.q16-dev@8:6.9.11.60+dfsg-1.3 > tiff/libtiff-dev@4.2.0-1 > tiff/libtiffxx5@4.2.0-1 > tiff/libtiff5@4.2.0-1
  and 3 more...

✗ High severity vulnerability found in imagemagick/imagemagick-6-common
  Description: Information Exposure
  Info: https://snyk.io/vuln/SNYK-DEBIAN11-IMAGEMAGICK-1246513
  Introduced through: imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3, imagemagick/libmagickwand-dev@8:6.9.11.60+dfsg-1.3, imagemagick@8:6.9.11.60+dfsg-1.3
  From: imagemagick/libmagickcore-dev@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6-common@8:6.9.11.60+dfsg-1.3
  From: imagemagick/libmagickwand-dev@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6-common@8:6.9.11.60+dfsg-1.3
  From: imagemagick@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6.q16@8:6.9.11.60+dfsg-1.3 > imagemagick/libmagickcore-6.q16-6@8:6.9.11.60+dfsg-1.3 > imagemagick/imagemagick-6-common@8:6.9.11.60+dfsg-1.3
  and 24 more...

✗ Critical severity vulnerability found in python3.9/libpython3.9-stdlib
  Description: Improper Input Validation
  Info: https://snyk.io/vuln/SNYK-DEBIAN11-PYTHON39-1290158
  Introduced through: mercurial@5.6.1-4
  From: mercurial@5.6.1-4 > python3-defaults/python3@3.9.2-3 > python3-defaults/libpython3-stdlib@3.9.2-3 > python3.9/libpython3.9-stdlib@3.9.2-1
  From: mercurial@5.6.1-4 > python3-defaults/python3@3.9.2-3 > python3.9@3.9.2-1 > python3.9/libpython3.9-stdlib@3.9.2-1
  From: mercurial@5.6.1-4 > python3-defaults/python3@3.9.2-3 > python3-defaults/python3-minimal@3.9.2-3 > python3.9/python3.9-minimal@3.9.2-1
  and 4 more...

✗ Critical severity vulnerability found in glibc/libc-bin
  Description: Use After Free
  Info: https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-1296898
  Introduced through: glibc/libc-bin@2.31-13+deb11u2, meta-common-packages@meta
  From: glibc/libc-bin@2.31-13+deb11u2
  From: meta-common-packages@meta > glibc/libc-dev-bin@2.31-13+deb11u2
  From: meta-common-packages@meta > glibc/libc6@2.31-13+deb11u2
  and 1 more...

Organization:      snyk-demo-567
Package manager:   deb
Project name:      docker-image|python-flask-app
Docker image:      python-flask-app
Platform:          linux/amd64
Base image:        python:3.8.12-bullseye
Licenses:          enabled

Tested 427 dependencies for known issues, found 171 issues.

Base Image              Vulnerabilities  Severity
python:3.8.12-bullseye  171              6 critical, 6 high, 27 medium, 132 low

Recommendations for base image upgrade:

Alternative image types
Base Image                   Vulnerabilities  Severity
python:3.9-slim              37               1 critical, 0 high, 1 medium, 35 low
python:3.11-rc-slim          37               1 critical, 0 high, 1 medium, 35 low
python:3.8.12-slim-bullseye  37               1 critical, 0 high, 1 medium, 35 low
python:3.10-slim-buster      70               2 critical, 9 high, 9 medium, 50 low

Temos 427 dependências na forma de bibliotecas de código aberto incluídas no conteúdo do sistema operacional Python 3.8. Elas introduzem um total de 171 vulnerabilidades de segurança nesta aplicação Flask em Python, devido à escolha da imagem-base python:3.8.

Neste momento, talvez você esteja se perguntando: "Como faço para corrigir isso?" Felizmente, a Snyk recomenda outras imagens-base para as quais você pode atualizar ou migrar, reduzindo assim a superfície de ataque.

Veja uma captura de tela com as recomendações para essa imagem-base:

A saída do terminal informa 171 vulnerabilidades em python:3.8.12-bullseye e compara a quantidade de vulnerabilidades em imagens base alternativas do Python.

Agora podemos tomar uma decisão embasada em dados para proteger nossa aplicação Python. Ao escolher qualquer uma das imagens Docker alternativas recomendadas pela Snyk, podemos reduzir significativamente a superfície de ataque do software incluído na aplicação.

Para ter ainda mais controle sobre a segurança da sua aplicação, conecte seus repositórios à interface da Snyk para importar seu código-fonte e o Dockerfile. Assim, além de encontrar essas vulnerabilidades, você poderá monitorá-las continuamente e identificar novos problemas de segurança. Veja o mesmo relatório da imagem-base do Docker na interface da Snyk:

Detalhes da imagem do Snyk Container mostrando Python 3.8 no Debian 11 e recomendações para atualizar a imagem-base, com a quantidade de vulnerabilidades.

O que é melhor do que monitorar e encontrar vulnerabilidades de segurança? Corrigi-las! :-)

Se você conectar seus repositórios Git à Snyk, também poderemos criar pull requests automaticamente no seu repositório para sugerir atualizações da imagem-base do Docker, como neste exemplo:

Solicitação de pull do GitHub mostrando uma atualização no carrinho/Dockerfile, de node:10 para node:debian-buster-slim, para corrigir vulnerabilidades

Se você gostou, confira esta publicação complementar sobre como automatizar a segurança de containers com pull requests para Dockerfiles.

Como aplicações Python são conteinerizadas?

Docker é uma tecnologia de virtualização de software que permite criar aplicações Python conteinerizadas reutilizáveis, multiplataforma e rápidas de implantar, com base em imagens Docker do Python. Essas aplicações são definidas como infraestrutura como código em um arquivo chamado Dockerfile.

Para criar e usar um aplicativo Python em contêiner, execute o seguinte:

docker build -t flask-application .
docker run -p 8080:5000 flask-application

Recomendações de segurança em Python para desenvolvedores

Essas práticas recomendadas ajudam você a criar, gerenciar e proteger melhor suas aplicações Python em containers. Se você gostou de conhecer essas práticas e valoriza a segurança de aplicações e a promoção da segurança de modo geral, recomendamos estes conteúdos para continuar aprendendo:

Segurança de contêineres que prioriza os desenvolvedores

O Snyk encontra e corrige automaticamente vulnerabilidades em imagens de contêiner e workloads do Kubernetes.

Leia mais

feature customer snowflake
Article

Segurança desde o início: conheça a integração do Snyk Studio com o Snowflake Cortex Code

O Snyk Studio se integra ao Snowflake Cortex Code para detectar vulnerabilidades em código gerado por IA, dependências e contêineres durante o desenvolvimento.

Blog

Verão nos estádios: a turnê Fan Zone do Snyk Connect

A turnê Fan Zone da Snyk levou workshops de segurança de IA, networking e competição amistosa a 8 cidades e 3 sessões virtuais. Os participantes desenvolveram habilidades, trocaram ideias e evoluíram juntos.

Blog

Snyk VulnBench JS 1.0: LLMs conseguem encontrar os mesmos bugs duas vezes?

Snyk VulnBench JS 1.0: 300 varreduras repetidas mostram que as descobertas de segurança dos LLMs variam a cada execução, enquanto SAST e modelos detectam diferentes lacunas de vulnerabilidade.