Skip to main content

Ferramentas de linha de comando para contêineres — usando Snyk com Buildah, Podman e Skopeo

Escrito por

9 de dezembro de 2020

0 minutos de leitura

Com o amadurecimento do ecossistema de contêineres, uma coisa não falta: opções — tanto de software quanto de maneiras de integrar tudo.

Uma dessas opções é a combinação de Buildah, Podman e Skopeo — três ferramentas de linha de comando de código aberto que surgiram no ecossistema RedHat. Como o nome sugere, Buildah oferece uma ampla gama de recursos para criar contêineres e imagens compatíveis com OCI; Podman permite gerenciar e executar contêineres OCI; e Skopeo é uma ferramenta para trabalhar com registros de contêineres.

Neste post, vamos ver como o uso de padrões abertos permite que a verificação de contêineres da Snyk funcione com todas essas tecnologias.

Primeiro, vamos conhecer o Skopeo. Essa é uma ferramenta de linha de comando para mover imagens de contêineres entre repositórios e também salvá-las localmente. O Skopeo permite gerar imagens como arquivos Docker ou arquivos OCI, formatos compatíveis com a Snyk ao verificar imagens no sistema de arquivos. Vamos usá-lo para baixar uma imagem do Quay.io e verificá-la localmente no nosso sistema de arquivos:

$ skopeo copy docker://quay.io/tutum/hello-world oci-archive:hello-world.tar
$ snyk container test oci-archive:hello-world.tar 

Testing oci-archive:hello-world.tar…

{-----OUTPUT SNIPPED-----}

✗ High severity vulnerability found in openssl
  Description: Out-of-Bounds
  Info: https://snyk.io/vuln/SNYK-UBUNTU1210-OPENSSL-374571
  Introduced through: openssl@1.0.1c-3ubuntu2.6, ca-certificates@20120623, ssl-cert@1.0.32, meta-common-packages@meta
  From: openssl@1.0.1c-3ubuntu2.6
  From: ca-certificates@20120623 > openssl@1.0.1c-3ubuntu2.6
  From: ssl-cert@1.0.32 > openssl@1.0.1c-3ubuntu2.6
  and 1 more...
  Fixed in: 1.0.1c-3ubuntu2.7

Organization:      matt-jarvis-snyk
Package manager:   deb
Project name:      docker-image|hello-world.tar
Docker image:      oci-archive:hello-world.tar
Licenses:          enabled

Tested 237 dependencies for known issues, found 25 issues.

Ubuntu 12.10 is no longer supported by the Ubuntu maintainers. Vulnerability detection may be affected by a lack of security updates.

Como podemos ver, essa imagem usa uma versão do Ubuntu que chegou ao fim da vida útil e, por isso, contém várias vulnerabilidades que não serão corrigidas. Com o Skopeo, podemos baixar imagens de qualquer registro de contêineres compatível com os padrões, em formatos aceitos pela Snyk, e verificá-las localmente.

Buildah

Agora, vamos conhecer o Buildah. O Buildah é uma ferramenta de linha de comando que cria imagens, mas não as executa. Por isso, foi pensado para ser usado em conjunto com o Podman. Ele também foi projetado para operar sem privilégios de root, usando namespaces de usuário no kernel do Linux para isolar um shell semelhante ao root, mas com apenas privilégios de usuário. Assim, administradores podem permitir que desenvolvedores criem e mantenham imagens de contêineres e, ao mesmo tempo, sigam o princípio do menor privilégio no ambiente de build.

Embora o Buildah possa trabalhar com Dockerfiles de forma semelhante ao próprio Docker, ele também permite desenvolver contêineres e imagens usando outros fluxos de trabalho, como Bash ou Python. Isso pode trazer mais flexibilidade e facilitar a depuração dos builds de contêineres. O Buildah permite, por exemplo, montar o contêiner intermediário durante o processo de build, para que scripts verifiquem se a execução ocorreu corretamente antes de continuar, compilem software externamente ou até solicitem dados ao usuário. Isso oferece uma alternativa aos processos de build em várias etapas, às vezes complicados, que se tornaram prática recomendada no Docker.

Vamos ver um exemplo prático de uso do Buildah.

Uma das primeiras coisas a observar no Buildah é que podemos usar Dockerfiles para criar nossos contêineres, se esse for o fluxo de trabalho ideal para você. Veja este Dockerfile como exemplo:

$ cat Dockerfile 
FROM centos:8
LABEL maintainer Matt Jarvis <matt@mattjarvis.org.uk>

RUN dnf install -y git gcc findutils make help2man texinfo gperf gettext-devel autoconf automake && dnf clean all

RUN git clone https://git.savannah.gnu.org/git/hello.git /opt/hello

WORKDIR /opt/hello

RUN ./bootstrap --skip-po && ./configure && make && make install

RUN ./hello
ENTRYPOINT "/usr/local/bin/hello"

Aqui usamos a imagem CentOS 8 como base, clonamos o código-fonte do GNU Hello, instalamos alguns pré-requisitos e, em seguida, compilamos e instalamos o binário.

Podemos fazer esse build automaticamente com o Buildah usando a opção bud (build-using-dockerfile):

$ buildah bud

Isso claramente não é uma boa prática, pois incluímos tanto o código-fonte quanto os pré-requisitos do build na imagem final, que também viola a maioria das boas práticas para o desenvolvimento de imagens seguras. Com o Docker, a prática recomendada nesse caso seria um build em várias etapas. Poderíamos usar um Dockerfile com várias etapas no Buildah, mas é ainda mais interessante usar comandos nativos do Buildah e escrever scripts para isso. Para reproduzir exatamente o que fizemos com o Dockerfile acima, executaríamos o seguinte script:

$ cat single-stage.sh 
#!/usr/bin/env bash

set -o errexit

# Create a container
container=$(buildah from centos:8)

# Labels are part of the "buildah config" command
buildah config --label maintainer="Matt Jarvis <matt@mattjarvis.org.uk>" $container

# Install pre-reqs
buildah run $container dnf install -y git gcc findutils make help2man texinfo gperf gettext-devel autoconf automake
buildah run $container dnf clean all

# Clone the git repository
buildah run $container git clone https://git.savannah.gnu.org/git/hello.git /opt/hello

# Workingdir is also a "buildah config" command
buildah config --workingdir /opt/hello $container

buildah run $container ./bootstrap --skip-po
buildah run $container ./configure
buildah run $container make
buildah run $container make install

# Entrypoint is also a “buildah config” command
buildah config --entrypoint /usr/local/bin/hello $container

# Finally save the running container to an image
buildah commit --format docker $container hello:latest

No entanto, o Buildah também permite montar o contêiner intermediário durante o processo de build. Assim, podemos compilar o código-fonte no host e simplesmente copiar o binário resultante para o contêiner antes de criar a imagem. Dessa forma, aproveitamos os recursos do host durante os builds e podemos fazer outras coisas, como usar o gerenciador de pacotes do host para instalar pacotes no contêiner. Também não precisamos de acesso root para isso, pois o Buildah permite usar namespaces de usuário para montar sem privilégios, com o comando unshare:

$ buildah unshare
$ cat host-mount.sh 
#!/usr/bin/env bash

set -o errexit

# Create a container
container=$(buildah from centos:8)
mountpoint=$(buildah mount $container)

buildah config --label maintainer="Matt Jarvis <matt@mattjarvis.org.uk>" $container

# Clone the git repository to the host
git clone https://git.savannah.gnu.org/git/hello.git hello

pushd hello
./bootstrap --skip-po
./configure
make
# Install to the container
make install DESTDIR=${mountpoint}
popd

chroot $mountpoint bash -c "/usr/local/bin/hello"

buildah config --entrypoint "/usr/local/bin/hello" $container
buildah commit --format docker $container hello
buildah unmount $container

Como você pode ver no exemplo acima, depois de executar o comando unshare, temos um shell root, mas em um namespace de usuário, sem privilégios completos de root. Isso permite montar o sistema de arquivos do contêiner durante o processo de build. Como se trata de um script Bash, também podemos aproveitar todos os recursos de uma linguagem de programação: usar condicionais e loops ou até solicitar dados ao usuário durante o processo.

Podman

Depois de executar esse script e criar nossa imagem, também podemos verificá-la com a Snyk — é aí que entra o Podman. O Podman é um mecanismo de contêineres sem daemon para desenvolver, gerenciar e executar contêineres OCI. Como Buildah e Podman são baseados em padrões abertos, a Snyk sempre conseguiu verificar imagens criadas ou baixadas pelo Podman: basta usá-lo para salvar a imagem em disco e verificá-la no sistema de arquivos. O Podman permite salvar imagens como arquivos Docker padrão ou no formato de arquivo OCI, ambos compatíveis com a Snyk.

[root@localhost buildah_test]# podman images
REPOSITORY                         TAG     IMAGE ID      CREATED         SIZE
localhost/hello                    latest  975f60fc1f19  15 minutes ago  223 MB
[root@localhost buildah_test]# podman save 975f60fc1f19 -o hello.tar
[root@localhost buildah_test]# snyk container test docker-archive:hello.tar

Testing docker-archive:hello.tar...

{-----OUTPUT SNIPPED-----}

✗ High severity vulnerability found in librepo
  Description: RHSA-2020:3658
  Info: https://snyk.io/vuln/SNYK-CENTOS8-LIBREPO-610057
  Introduced through: librepo@1.11.0-2.el8
  From: librepo@1.11.0-2.el8
  Fixed in: 0:1.11.0-3.el8_2

Organization:      matt-jarvis-snyk
Package manager:   rpm
Project name:      docker-image|hello.tar
Docker image:      docker-archive:hello.tar
Platform:          linux/amd64
Licenses:          enabled

Tested 172 dependencies for known issues, found 28 issues.

Pro tip: use `--file` option to get base image remediation advice.
Example: $ snyk test --docker docker-archive:hello.tar --file=path/to/Dockerfile

To remove this message in the future, please run `snyk config set disableSuggestions=true`

Como nossa imagem base é pública e está no Dockerhub, também podemos simplesmente fazer o seguinte para verificá-la com a Snyk CLI:

[root@localhost buildah_test]# snyk container test centos:8

Nesse caso, a Snyk interage diretamente com o Dockerhub e verifica a imagem original.

No entanto, se a imagem existir apenas localmente no Podman ou estiver em um repositório privado no Dockerhub que exige login, nenhuma dessas opções funcionará. Para demonstrar, vamos tentar verificar nossa imagem criada diretamente no Podman:

[matt@localhost buildah_test]# podman images
REPOSITORY                         TAG     IMAGE ID      CREATED         SIZE
localhost/hello                    latest  975f60fc1f19  20 minutes ago  223 MB

[matt@localhost buildah_test]# snyk container test localhost/hello
connect ECONNREFUSED 127.0.0.1:443

Para esses casos de uso, a Snyk utiliza o cliente local do Docker, seja pela API do registro, seja pelo socket do Docker. Como não há um binário do Docker no sistema, a Snyk tentou usar a API do registro e não conseguiu se conectar. Por padrão, a Snyk não sabe que essas imagens existem localmente no Podman nem como usá-lo para se comunicar com a origem.

Felizmente, o Podman oferece recursos simples de compatibilidade por meio do pacote podman-docker. Ele cria um binário falso do Docker, que basicamente é um wrapper de shell para o binário do Podman, e adiciona um link simbólico entre o socket da API do Podman e o local padrão do arquivo de socket do Docker. Por padrão, esse pacote cria um link simbólico para /var/run/podman/podman.sock, que é o local padrão quando a API do Podman é executada como root.

[matt@localhost buildah_test]$ sudo dnf install podman-docker

Now let’s try and scan our local image again :

[matt@localhost buildah_test]$ snyk container test localhost/hello
connect EACCES /var/run/docker.sock

Ainda não funciona, mas desta vez recebemos um erro diferente. A Snyk detectou um binário chamado docker e agora está tentando se conectar ao local padrão do socket privilegiado do Docker. Como ainda não estamos executando a API do Podman, a conexão falha novamente.

Como vimos antes, o pacote podman-docker cria um link simbólico de /var/run/podman/podman.sock para /var/run/docker.sock, supondo que a API do Podman será executada com privilégios.

[matt@localhost buildah_test]$ ls -l /var/run/docker.sock 
lrwxrwxrwx. 1 root root 23 Nov 25 07:51 /var/run/docker.sock -> /run/podman/podman.sock

Um dos principais problemas do socket do Docker é que, por padrão, ele é executado com privilégios, o que é considerado um risco de segurança em determinados ambientes. Na maioria dos ambientes locais de desenvolvimento, não precisamos executar a API do Podman dessa forma: podemos usar um socket sem privilégios em outro local. Também podemos informar esse local à Snyk usando a variável de ambiente DOCKER_HOST, que aceita os formatos de URL tcp:// e unix://.

Em outro shell, vamos executar a API do Podman com um socket sem privilégios:

[matt@localhost ~]$ podman system service --time=0 unix://home/matt/podman.sock

Esse processo será executado em primeiro plano, então você precisará pressionar Ctrl+C para encerrá-lo ao terminar o teste. Agora, precisamos definir a variável de ambiente DOCKER_HOST:

[matt@localhost buildah_test]$ export DOCKER_HOST=unix:///home/matt/podman.sock

Por fim, vamos tentar verificar novamente nossa imagem local com a Snyk:

[matt@localhost buildah_test]$ snyk container test localhost/hello

Testing localhost/hello...

{ ----SNIPPED OUTPUT----}

✗ High severity vulnerability found in librepo
  Description: RHSA-2020:3658
  Info: https://snyk.io/vuln/SNYK-CENTOS8-LIBREPO-610057
  Introduced through: librepo@1.11.0-2.el8
  From: librepo@1.11.0-2.el8
  Fixed in: 0:1.11.0-3.el8_2

Organization:      matt-jarvis-snyk
Package manager:   rpm
Project name:      docker-image|localhost/hello
Docker image:      localhost/hello
Platform:          linux/amd64
Licenses:          enabled

Tested 172 dependencies for known issues, found 28 issues.

Pro tip: use `--file` option to get base image remediation advice.
Example: $ snyk test --docker localhost/hello --file=path/to/Dockerfile

To remove this message in the future, please run `snyk config set disableSuggestions=true`

Funcionou! Agora a Snyk está operando corretamente e se comunicando com o Podman pelo socket sem privilégios.

Para configurar o DOCKER_HOST permanentemente no seu shell, você pode adicionar o comando export ao arquivo .bashrc para manter essa configuração.

Por fim, queremos que toda essa configuração esteja disponível automaticamente sempre que fizermos login. Para isso, podemos aproveitar os recursos de ativação por socket do systemd, que iniciam nosso socket automaticamente quando fazemos login e tentamos nos conectar a ele.

Primeiro, precisamos configurar o socket:

[matt@localhost ~]$ cat .config/systemd/user/podman.socket
[Unit]
Description=Podman API Socket
Documentation=man:podman-api(1)

[Socket]
ListenStream=/home/matt/podman.sock
SocketMode=0660

[Install]
WantedBy=sockets.target

Depois, associamos o socket a um serviço:

[matt@localhost ~]$ cat .config/systemd/user/podman.service
[Unit]
Description=Podman API Service
Requires=podman.socket
After=podman.socket
Documentation=man:podman-api(1)
StartLimitIntervalSec=0

[Service]
Type=oneshot
Environment=REGISTRIES_CONFIG_PATH=/etc/containers/registries.conf
ExecStart=/usr/bin/podman system service unix:///home/matt/podman.sock
TimeoutStopSec=30
KillMode=process

[Install]
WantedBy=multi-user.target
Also=podman.socket

Por fim, recarregamos a configuração do systemd e habilitamos o serviço!

[matt@localhost ~]$ systemctl --user daemon-reload
[matt@localhost ~]$ systemctl --user enable --now podman.socket

Conclusão

Pronto: a verificação de imagens da Snyk CLI com o Podman funciona exatamente como com o Docker. Assim, desenvolvedores podem verificar de forma abrangente a segurança de imagens Docker ou OCI locais como parte do fluxo de desenvolvimento, sem precisar de privilégios elevados. Também vimos como usar Skopeo e Buildah com a Snyk graças aos padrões abertos!

Ainda não tem uma conta na Snyk? Cadastre-se gratuitamente e use a Snyk para verificar imagens de contêineres e dependências de código aberto.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo sob demanda ao nosso workshop virtual introdutório.