Chegamos a um ponto sem retorno no gerenciamento de dependências de software?
Os problemas de segurança da cadeia de fornecimento de software estão afetando fortemente todo o ecossistema OSS; não passa um dia sem que um incidente de segurança aconteça, afetando usuários e empresas desatentos com software construído com os padrões modernos de ultracombinabilidade feitos de um denso número de dependências externas em múltiplas camadas.
De acordo com a pesquisa realizada pela Sonatype em seu relatório anual State of Software Supply Chain , os ataques à cadeia de suprimentos têm um aumento médio de 742% ao ano.
Existem várias razões por trás disso, como a maior demanda do mercado por software em todos os setores e o tremendo crescimento do modelo de código aberto; estima-se que 90% das empresas utilizam código aberto.
Outro aspecto que sustenta esta situação é a descoberta e a evolução de ataques cibernéticos especificamente concebidos para anexar a cadeia de abastecimento, como a Confusão de Dependência, o Typosquatting e o seu Primo-Brandjacking, as Injecções de Código Malicioso e o Protestware.
De acordo com dados da indústria , o número médio de dependências transitivas (indiretas) para um projeto JavaScript no GitHub é 683.
As dependências continuam sendo um dos mecanismos preferidos para criar e distribuir pacotes maliciosos, e ainda é relativamente fácil forjá-los.
Em fevereiro de 2022, o GitHub introduziu a autenticação obrigatória de dois fatores para os 100 principais mantenedores de npm e o PyPA está trabalhando para reduzir a dependência de setup.py , que é um elemento-chave para como esses ataques podem ser lançados ao mesmo tempo em que promovem a adoção de 2FA usando um painel público ( Fonte: https://www.sonatype.com/state-of-the-software-supply-chain/open-source-supply-demand-security )
Para ver os números em ação, testaremos duas das linguagens mais utilizadas em 2022, que são Javascript e Python, e por fim um projeto baseado em Microsserviços composto por diversas linguagens.
JavaScript
Indo inicializar uma base de código Javascript NextJS simples usando as configurações padrão fornecidas:
❯ npx create-next-app@latest
✔ What is your project named? … supply-chain
✔ Would you like to use TypeScript with this project? … No / Yes
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Use App Router (recommended)? … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
Creating a new Next.js app in /Users/paolomainardi/temp/supply-chain.
Using npm.
Initializing project with template: app-tw
Installing dependencies:
- react
- react-dom
- next
- typescript
- @types/react
- @types/node
- @types/react-dom
- tailwindcss
- postcss
- autoprefixer
- eslint
- eslint-config-next
added 344 packages, and audited 345 packages in 4s
127 packages are looking for funding
run `npm fund` for details
5 moderate severity vulnerabilities
To address all issues, run:
npm audit fix
Run `npm audit` for details.
Initialized a git repository.
Success! Created supply-chain at /dev/supply-chain
Uma coisa a observar é que um aplicativo recém-instalado já possui 5 vulnerabilidades de gravidade moderada . Essa forma de entregar mensagens é arriscada porque inadvertidamente ensina nosso cérebro a ignorá-las, sejam elas úteis ou não. Na próxima vez que um aviso como esse aparecer, ele poderá passar despercebido. Deveríamos melhorar a forma como apresentamos estas mensagens.
Vamos agora contar as dependências:
❯ tree -d supply-chain/node_modules -L 1 | tail -n 1
298 directories
Para imprimir um Hello World com NextJS, precisamos trazer 298 dependências com 5 vulnerabilidades conhecidas .
Empacotamos o aplicativo em um contêiner OCI, conforme explicado aqui no documento oficial :
❯ curl -Lo Dockerfile https://raw.githubusercontent.com/vercel/next.js/canary/examples/with-docker/Dockerfile
❯ # Edit this file according to the documentation to produce a standalone build.
// next.config.js
module.exports = {
// ... rest of the configuration.
output: 'standalone',
}
❯ docker build -t supply-chain-next-docker .
[+] Building 33.0s (19/19) FINISHED
=> => naming to docker.io/library/supply-chain-next-docker 0.0s
❯ syft supply-chain-next-docker > deps
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [282 packages]
❯ grep apk deps | wc -l
17
❯ grep npm deps | wc -l
249
# Scan for known vulnerabilities.
❯ grype supply-chain-next-docker
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [282 packages]
✔ Scanning image... [1 vulnerabilities]
├── 0 critical, 0 high, 1 medium, 0 low, 0 negligible
└── 1 fixed
NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
semver 7.3.8 7.5.2 npm GHSA-c2qf-rxjj-qqgw Medium
Nesse caso, a compilação produzida pelo NextJS é um pouco menor em termos de pacotes ( por causa do modo autônomo ), e a imagem base Alpine adiciona apenas 17 pacotes no momento sem nenhuma vulnerabilidade conhecida.
Esta é uma imagem pronta para ser enviada em produção, e conta com 282 pacotes (NPM + Alpine) sem nenhuma modificação feita por nós; é apenas a estrutura básica.
Os grandes tamanhos de pacotes no NodeJS se devem principalmente à biblioteca padrão JavaScript relativamente pequena e à filosofia inspirada no Unix por trás do NodeJS, conforme explicado nesta postagem do blog .
A filosofia mencionada às vezes pode levar a distorções, como no caso de pacotes como “left-pad” se tornarem parte de muitos pacotes (mesmo como uma dependência transitiva). Isso quase causou o colapso da internet quando o autor decidiu removê-lo devido a uma disputa de nome. Você pode ler mais sobre isso aqui.
Acho que este evento também inspirou este famoso meme do xkcd :
Pitão
É a segunda linguagem mais usada e de crescimento mais rápido, com mais de 22% ano após ano (fonte: https://github.blog/2023-03-02-why-python-keeps-growing-explained/ ).
Para comparar com Javascript, usarei Django como caso de teste.
❯ cat app/requirements.txt
Django==4.2.2
❯ cat app/Dockerfile
FROM python:3.10-alpine
EXPOSE 8000
WORKDIR /app
COPY requirements.txt /app
RUN pip3 install -r requirements.txt --no-cache-dir
COPY . /app
ENTRYPOINT ["python3"]
CMD ["manage.py", "runserver", "0.0.0.0:8000"]
❯ syft django-web
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [46 packages]
❯ grep apk deps | wc -l
38
❯ grep python deps | wc -l
8
❯ grep binary deps | wc -l
2
# Scan for known vulnerabilities.
❯ grype django-web
✔ Vulnerability DB [no update available]
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [46 packages]
✔ Scanning image... [2 vulnerabilities]
├── 0 critical, 1 high, 1 medium, 0 low, 0 negligible
└── 0 fixed
NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
pip 23.1.2 python CVE-2018-20225 High
python 3.11.4 binary CVE-2007-4559 Medium
A situação aqui é melhor; Django tem apenas 8 dependências (pelo menos as descobertas por Syft ).
Como Python é a melhor linguagem para IA, eu queria tentar adicionar PyTorch como uma dependência do projeto:
❯ cat app/requirements.txt
Django==4.2.2
--extra-index-url https://download.pytorch.org/whl/cpu
torch
torchvision
torchaudio
# We need a glibc base image to install pytorch.
❯ cat app/Dockerfile
FROM python:3.11.4
...
❯ grep binary deps | wc -l
2
❯ grep python deps | wc -l
35
❯ grep deb deps | wc -l
429
❯ grype django-web
✔ Vulnerability DB [no update available]
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [455 packages]
✔ Scanning image... [678 vulnerabilities]
├── 1 critical, 41 high, 111 medium, 30 low, 464 negligible (31 unknown)
└── 3 fixed
Mesmo com o PyTorch e suas dependências, o número de pacotes Python é relativamente pequeno, apenas 35; o que é muito maior agora é o número de pacotes transportados da imagem base do Debian (necessários para usar PyTorch em vez de Alpine), para ser mais preciso, 429 pacotes com um número absurdo de vulnerabilidades conhecidas , mesmo que esta imagem seja o Python estável mais recente Versão 3.11 .
Arquitetura e dependências de microsserviços
Para este teste, usarei um projeto muito interessante do GCP: Online Boutique é um aplicativo de demonstração de microsserviços que prioriza a nuvem e tem como objetivo principal demonstrar o uso de tecnologias como Kubernetes, GKE, Istio, Stackdriver e gRPC e é composto por 11 microsserviços em 5 linguagens diferentes : Go, Node.js, Python, Java, C#
- Outra suposição importante a ser feita aqui é que este projeto devido à sua natureza é mais otimizado nas partes da nuvem do que nos aspectos de desenvolvimento, como dependências
# Calculate packages and vulnerabilities for each microservice.
DOCKER_IMAGES="gcr.io/google-samples/microservices-demo/emailservice:v0.8.0,
gcr.io/google-samples/microservices-demo/checkoutservice:v0.8.0,
gcr.io/google-samples/microservices-demo/recommendationservice:v0.8.0,
gcr.io/google-samples/microservices-demo/frontend:v0.8.0,
gcr.io/google-samples/microservices-demo/paymentservice:v0.8.0,
gcr.io/google-samples/microservices-demo/productcatalogservice:v0.8.0,
gcr.io/google-samples/microservices-demo/cartservice:v0.8.0,
gcr.io/google-samples/microservices-demo/loadgenerator:v0.8.0,
gcr.io/google-samples/microservices-demo/currencyservice:v0.8.0,
gcr.io/google-samples/microservices-demo/shippingservice:v0.8.0,
gcr.io/google-samples/microservices-demo/adservice:v0.8.0"
for image in $(echo $DOCKER_IMAGES | sed "s/,/ /g")
do
echo "Scanning image: $image"
syft $image > /dev/null
grype $image > /dev/null
done%
Vamos eliminar as vulnerabilidades abaixo das altas e formatar os dados em uma tabela:
Microsserviço | Linguagem | Pacotes | ≥ Altas vulnerabilidades |
---|---|---|---|
serviço de e-mail | Pitão | 152 | 39 |
serviço de checkout | Ir | 52 | 2 |
serviço de recomendação | Pitão | 147 | 39 |
front-end | Ir | 71 | 8 |
serviço de pagamento | Node.js | 626 | 5 |
catálogo de produtosserviço | Ir | 51 | 5 |
serviço de carrinho | C# | 25 | 0 |
gerador de carga | Python/Gafanhoto | 137 | 33 |
serviço de moeda | Node.js | 649 | 5 |
serviço de entrega | Ir | 37 | 3 |
serviço de anúncios | Java | 112 | 19 |
TOTAL | 2059 | 158 |
Aqui podemos ver toda a superfície deste aplicativo baseado em microsserviços, um aglomerado de linguagem de programação e dependências de sistema operacional, para 2.095 pacotes com 158 vulnerabilidades conhecidas altas e críticas , números enormes.
Node.js, novamente, é o que mais exige dependências. Em vez disso, o Python tem menos dependências, mas mais vulnerabilidades abertas; Java está muito próximo do Python com os números.
O vencedor claro neste cenário é o C#, apenas 25 bibliotecas agregadas e 0 vulnerabilidades.
Pensamentos finais
Sonatype resume bem o crescimento ininterrupto do ecossistema OSS:
Os dados mostram que:
- O desenvolvimento de código aberto está mais florido do que nunca; OSS venceu .
- O crescimento está espalhado por todos os maiores ecossistemas, o que é muito bom para toda a indústria; não existe um ator monopolista.
- Um ecossistema médio possui 3,3 milhões de projetos , cada um com uma média de 14 versões lançadas anualmente, o que é bom porque o projeto é mantido e ruim porque requer manutenção.
Hoje estamos expostos a uma grande complexidade escondida por ferramentas muito poderosas.
Obter uma nova dependência é uma tarefa que exige muito pouco esforço; com um gerenciador de pacotes moderno, podemos montar sistemas operacionais e aplicativos inteiros compostos por centenas de milhares de dependências externas, com apenas um ou poucos comandos fáceis, e isso é francamente poderoso e assustador
Em 1984, Edsger Wybe Dijkstra no artigo “ Sobre a natureza da Ciência da Computação ” disse que “A simplicidade é uma grande virtude mas requer muito trabalho para alcançá-la e educação para apreciá-la. E para piorar: a complexidade vende melhor”.
Pela minha experiência pessoal, em muitos anos neste setor, um dos maiores inimigos desta indústria são as soluções de tamanho único que podem resolver e corrigir magicamente qualquer problema, em qualquer contexto, no que diz respeito à complexidade do problema, este nunca é o caso, é melhor focar na complexidade do problema.
Por exemplo, se eu precisar enviar um site estático com um monte de HTML e CSS, nunca terei a necessidade de implantá-lo em um cluster Kubernetes; em vez disso, tudo que preciso é de um servidor da web não tão sofisticado.
O mesmo se aplica aos microsserviços, nem sempre é o ajuste certo, mesmo quando o cenário pode sugerir o contrário, há casos importantes como Istio ou Amazon Prime Video.
E agora, voltando à questão deste artigo.
A resposta é sim, já ultrapassamos o ponto sem volta e isso não é necessariamente uma coisa ruim, pelo contrário, acho que estamos vivendo a era de ouro desta indústria e temos tantas oportunidades como profissionais ou cientistas como nunca antes. .
No entanto, isto também significa que a maioria dos projetos depende de grandes quantidades de código que podem ser vulneráveis ou maliciosos. Isto causa estresse para os desenvolvedores e representa riscos significativos para todo o setor. Em alguns casos, pode até pôr em perigo a segurança nacional, como demonstrado pelo caso Solarwinds .
Hoje, a criação de um novo produto de software deve levantar algumas questões fundamentais, tais como:
- Como podemos confiar que alguém diferente de nós sempre poderá atuar como um bom ator?
- Como podemos ter certeza de que todo o código do qual dependemos é feito exclusivamente para fazer o que afirma?
- Como podemos ter certeza de que o software não foi adulterado em um dos pontos da cadeia de abastecimento ?
Em 1984, Ken Thompson, no famoso artigo “ Reflexões sobre a confiança na confiança ”, basicamente fez as mesmas perguntas, e a moral, que considero ainda válida, diz que:
- “Você não pode confiar em um código que você mesmo não criou totalmente. Nenhuma verificação ou escrutínio no nível da fonte protegerá o uso de código confiável. Até que ponto se deve confiar numa afirmação de que um programa está livre de cavalos de Tróia? Talvez seja mais importante confiar nas pessoas que escreveram o software.”
Embora tenha sido possível conhecer os desenvolvedores que criaram e utilizaram bibliotecas de terceiros no passado, hoje não é tão viável devido ao aumento do número de atores envolvidos. Além disso, os ataques Protestware podem representar uma forte ameaça que não pode ser facilmente combatida.
Aprendizado
Então, aqui estão 8 tarefas práticas :
- Não caia na armadilha de usar muitas bibliotecas externas, pense duas vezes se realmente precisar delas, em caso de dúvida pense em um desastre com o teclado esquerdo .
- Microsserviços vs Monolith não é escolher o bom ou o ruim, concentre-se no problema, mais simples é sempre melhor.
- Use a estrutura SLSA para compreender as ameaças modernas à cadeia de fornecimento de software e trabalhar para adotar níveis de segurança definidos, um passo de cada vez.
- Automatize o gerenciamento de dependências usando uma ferramenta OSS como o RenovateBot , é agradável e fácil de integrar.
- Abrace a nova era de assinatura de artefatos usando atestados Sigstore e SLSA para produzir seus artefatos e como um guia para escolher software de terceiros que os adote.
- Sempre produza Lista de Materiais de Software (SBOM) para um artefato e finja que também o recebeu de seus fornecedores. ( Padrões SPDX e CycloneDX )
- Automatize e verifique vulnerabilidades conhecidas em todo o seu conjunto de dependências. É fácil fazer isso ao padronizar os artefatos para contêineres OCI. Usando uma ferramenta como o Grype .
- Use imagens Docker menores, como Distroless ou Chainguard, quando possível
Artigo originalmente publicado por CNCF.IO
DNX Brasil – Soluções cloud-native