Este documento descreve as dependências do aplicativo e as práticas recomendadas para gerenciá-las, incluindo monitoramento de vulnerabilidades, verificação de artefatos, redução da pegada de dependências e suporte a builds reproduzíveis.
Uma dependência de software é um software necessário para o funcionamento do aplicativo, como uma biblioteca de software ou um plug-in. A resolução de dependências pode acontecer quando você compila o código, cria, executa, faz o download ou instala o software.
As dependências podem incluir componentes criados por você, softwares de terceiros e softwares de código aberto. A abordagem adotada para gerenciar dependências pode afetar a segurança e a confiabilidade dos aplicativos.
As especificidades para implementar as práticas recomendadas podem variar de acordo com o formato do artefato e as ferramentas usadas, mas os princípios gerais ainda se aplicam.
Dependências diretas e transitivas
Os aplicativos podem incluir dependências diretas e transitivas:
- Dependências diretas
- Componentes de software aos quais um aplicativo faz referência diretamente.
- Dependências transitivas
- Componentes de software que as dependências diretas de um aplicativo exigem funcionalmente. Cada dependência pode ter dependências diretas e indiretas, criando uma árvore recursiva de dependências transitivas que afetam o aplicativo.
Diferentes linguagens de programação oferecem diferentes níveis de visibilidade nas dependências e nas relações delas. Além disso, algumas linguagens usam gerenciadores de pacotes para resolver a árvore de dependências ao instalar ou implantar um pacote.
No ecossistema do Node.js, os gerenciadores de pacotes npm e yarn usam arquivos de bloqueio para identificar versões de dependência para criar um módulo e as versões de dependência que um gerenciador de pacotes faz o download para uma instalação específica do módulo. Em outros ecossistemas de linguagem, como Java, há um suporte mais limitado para a introspecção de dependências. Além disso, os sistemas de build precisam usar gerenciadores de dependência específicos para gerenciar as dependências de forma sistemática.
Como exemplo, considere o módulo npm glob
versão 8.0.2. Declare dependências
diretas para módulos do npm no arquivo package.json
. No
arquivo package.json para glob, a
seção dependencies
lista as dependências diretas do pacote publicado.
A seção devDepdencies
lista as dependências para desenvolvimento local e
testes por mantenedores e colaboradores de glob
.
No site do npm, a página glob lista as dependências diretas e de desenvolvimento, mas não indica se esses módulos também têm dependências próprias.
Confira mais informações sobre dependências de
glob
no site do Open Source Insights. A lista de dependências para glob inclui dependências diretas e indiretas (transitivas).Uma dependência transitiva pode ter várias camadas na árvore de dependências. Exemplo:
- O
glob
8.0.2 tem uma dependência direta dominimatch
5.0.1. - O
minimatch
5.0.1 tem uma dependência direta dobrace-expression
2.0.1. - O
brace-expression
2.0.1 tem uma dependência direta dobalanced-match
1.0.2.
- O
Sem visibilidade sobre dependências indiretas, é muito difícil identificar e responder a vulnerabilidades e outros problemas que se originam de um componente que o código não referencia diretamente.
Quando você instala o pacote glob
, o npm resolve toda a árvore de dependências
e salva a lista de versões baixadas específicas no arquivo
package.lock.json para que você tenha um registro
de todas as dependências. Instalações subsequentes no mesmo ambiente vão
extrair as mesmas versões.
Ferramentas para insights de dependência
Use as ferramentas a seguir para entender suas dependências de código aberto e avaliar a postura de segurança dos seus projetos. Essas ferramentas fornecem informações sobre os formatos de pacotes.
- Insights de segurança no console do Google Cloud
- O Google Cloud oferece insights de segurança para seus artefatos no Cloud Build, Cloud Run e GKE, incluindo vulnerabilidades, informações de dependência, SBOM (software bill of materials) e origem do build. Outros serviços do Google Cloud também oferecem recursos que melhoram sua postura de segurança em todo o ciclo de vida do desenvolvimento de software. Para saber mais, consulte a visão geral da segurança da cadeia de suprimentos de software.
- Ferramentas de código aberto
Várias ferramentas de código aberto estão disponíveis, incluindo:
Open Source Insights: um site que fornece informações sobre dependências diretas e indiretas conhecidas, vulnerabilidades conhecidas e informações de licença para softwares de código aberto. O projeto Open Source Insights também disponibiliza esses dados como um conjunto de dados do Google Cloud. Use o BigQuery para analisar os dados.
Banco de dados de vulnerabilidades de código aberto: um banco de dados de vulnerabilidades pesquisáveis que agrega vulnerabilidades de outros bancos de dados em um local.
Scorecards: uma ferramenta automatizada que pode ser usada para identificar práticas arriscadas da cadeia de suprimentos de software nos seus projetos do GitHub. Ele realiza verificações em repositórios e atribui a cada verificação uma pontuação de 0 a 10. Em seguida, use as pontuações para avaliar a postura de segurança do projeto.
Allstar: um app do GitHub que monitora continuamente as organizações ou os repositórios do GitHub para verificar a adesão às políticas configuradas. Por exemplo, é possível aplicar uma política à sua organização do GitHub que verifica se há colaboradores de fora da organização que têm acesso de administrador ou push.
Abordagens para incluir dependências
Há vários métodos comuns para incluir dependências no aplicativo:
- Instalar diretamente de fontes públicas
- Instale dependências de código aberto diretamente de repositórios públicos, como Docker Hub, npm, PyPI ou Maven Central. Essa abordagem é conveniente porque você não precisa manter suas dependências externas. No entanto, como você não controla essas dependências externas, sua cadeia de suprimentos de software é mais propensa a ataques de cadeia de suprimentos de código aberto.
- Armazenar cópias de dependências no repositório de origem
- Essa abordagem também é conhecida como venda de produtos. Em vez de instalar uma dependência
externa de um repositório público durante os builds, faça o download e
copie para a árvore de origem do projeto. Você tem mais controle sobre as dependências
fornecidas pelo fornecedor que usa, mas há várias desvantagens:
- As dependências disponibilizadas por pacotes de terceiros aumentam o tamanho do repositório de origem e aumentam a rotatividade.
- Você precisa vender as mesmas dependências em cada aplicativo separado. Se o repositório de origem ou o processo de build não oferecer suporte a módulos de origem reutilizáveis, talvez seja necessário manter várias cópias das dependências.
- Fazer upgrade de dependências disponibilizadas por pacotes de terceiros pode ser mais difícil.
- Armazenar dependências em um registro particular
Um registro privado, como o Artifact Registry, oferece a conveniência da instalação de um repositório público, além de controle sobre suas dependências. Com o Artifact Registry, você pode:
- Centralize os artefatos e as dependências de build de todos os seus aplicativos.
- Configure seus clientes de pacotes de linguagem e do Docker para interagir com repositórios particulares no Artifact Registry da mesma forma que eles fazem com repositórios públicos.
Tenha mais controle sobre suas dependências em repositórios particulares:
- Restrinja o acesso a cada repositório com o Identity and Access Management.
- Use repositórios remotos para armazenar em cache as dependências de origens públicas upstream e verificar se há vulnerabilidades (pré-visualização privada).
- Use repositórios virtuais para agrupar repositórios remotos e privados atrás de um único endpoint. Defina uma prioridade em cada repositório para controlar a ordem de pesquisa ao fazer o download ou instalar um artefato (pré-visualização particular).
Use o Artifact Registry com outros serviços do Google Cloud, incluindo o Cloud Build, o Cloud Run e o Google Kubernetes Engine. Use a verificação automática de vulnerabilidades em todo o ciclo de vida de desenvolvimento de software, gere a origem do build, controle implantações e confira insights sobre sua postura de segurança.
Sempre que possível, use um registro particular para suas dependências. Em situações em que não é possível usar um registro particular, considere vender suas dependências para ter controle sobre o conteúdo na cadeia de suprimentos de software.
Fixação de versão
A fixação de versão significa restringir uma dependência do aplicativo a uma versão específica ou a um intervalo de versões. O ideal é fixar uma única versão de uma dependência.
Fixar a versão de uma dependência ajuda a garantir que os builds do aplicativo sejam reproduzíveis. No entanto, isso também significa que seus builds não incluem atualizações da dependência, incluindo correções de segurança, de bugs ou melhorias.
É possível atenuar esse problema usando ferramentas automatizadas de gerenciamento de dependências que monitoram dependências nos repositórios de origem para novas versões. Essas ferramentas fazem atualizações nos arquivos de requisitos para atualizar as dependências conforme necessário, muitas vezes incluindo informações do registro de alterações ou outros detalhes.
A fixação de versão se aplica apenas a dependências diretas, não a dependências
transitivas. Por exemplo, se você fixar a versão do pacote my-library
,
o pin restringe a versão de my-library
, mas não restringe as versões
do software que my-library
tem dependência. É possível restringir a árvore de dependências
de um pacote em alguns idiomas usando um arquivo de bloqueio.
Verificação de assinatura e hash
Há vários métodos que podem ser usados para verificar a autenticidade de um artefato que você está usando como dependência.
- Verificação de hash
Um hash é um valor gerado para um arquivo que funciona como um identificador exclusivo. É possível comparar o hash de um artefato com o valor de hash calculado pelo provedor do artefato para confirmar a integridade do arquivo. A verificação de hashes ajuda a identificar a substituição, a adulteração ou a corrupção de dependências por um ataque man-in-the-middle ou comprometimento do repositório de artefatos.
O uso da verificação de hash exige que você confie que o hash recebido do repositório de artefatos não está comprometido.
- Verificação de assinatura
A verificação de assinatura aumenta a segurança do processo de verificação. O repositório de artefatos, os mantenedores do software ou ambos podem assinar artefatos.
Serviços como o sigstore oferecem uma maneira de os mantenedores assinarem artefatos de software e de os consumidores verificarem essas assinaturas.
A autorização binária pode verificar se as imagens de contêiner implantadas nos ambientes de execução do Google Cloud são assinhadas com atestados para vários critérios.
Arquivos de bloqueio e dependências compiladas
Os arquivos de bloqueio são arquivos de requisitos totalmente resolvidos, que especificam exatamente qual versão de cada dependência precisa ser instalada para um aplicativo. Normalmente produzidos automaticamente por ferramentas de instalação, os arquivos de bloqueio combinam a fixação de versão e a verificação de assinatura ou hash com uma árvore de dependência completa para o aplicativo.
As ferramentas de instalação criam árvores de dependências resolvendo totalmente todas as dependências transitivas a jusante das dependências de nível superior e, em seguida, incluem a árvore de dependências no arquivo de bloqueio. Como resultado, apenas essas dependências podem ser instaladas, tornando os builds mais reproduzíveis e consistentes.
Como misturar dependências públicas e privadas
Os aplicativos modernos nativos da nuvem geralmente dependem de código de terceiros de código aberto e de bibliotecas internas de código fechado. Artifact Registry permite compartilhar sua lógica de negócios em vários aplicativos e reutilizar a mesma ferramenta para instalar bibliotecas externas e internas.
No entanto, ao misturar dependências privadas e públicas, sua cadeia de suprimentos de software fica mais vulnerável a um ataque de confusão de dependências. Ao publicar projetos com o mesmo nome do seu projeto interno em repositórios de código aberto, os invasores podem aproveitar os instaladores configurados incorretamente para instalar o código malicioso em vez da dependência interna.
Para evitar um ataque de confusão de dependência, siga estas etapas:
- Inclua as assinaturas ou hashes das suas dependências em um arquivo de bloqueio para verificar.
- Separe a instalação de dependências internas e de terceiros em duas etapas distintas.
- Espelhe explicitamente as dependências de terceiros necessárias no repositório particular, manualmente ou com um proxy de pull-through. Os repositórios remotos do Artifact Registry são proxies de pull-through para repositórios públicos upstream.
- Use repositórios virtuais para consolidar repositórios remotos e padrão do Artifact Registry em um único endpoint. É possível configurar prioridades para repositórios upstream para que as versões de artefatos privados sejam sempre priorizadas em relação a artefatos públicos com o mesmo nome.
- Use fontes confiáveis para pacotes públicos e imagens de base.
- Use o Assured Open Source Software para acessar imagens Java e Python conhecidas que foram testadas e verificadas pelo Google.
- Use imagens de base fornecidas pelo Google ou um pipeline de imagens seguro para gerar suas próprias imagens de base.
Como remover dependências não usadas
À medida que suas necessidades mudam e seu aplicativo evolui, você pode mudar ou parar de usar algumas das dependências. Continuar instalando dependências não utilizadas com seu aplicativo aumenta o impacto da dependência e o risco de comprometimento por uma vulnerabilidade nessas dependências.
Depois que o aplicativo estiver funcionando localmente, uma prática comum é copiar todas as dependências instaladas durante o processo de desenvolvimento para o arquivo de requisitos do aplicativo. Em seguida, você implanta o aplicativo com todas essas dependências. Essa abordagem ajuda a garantir que o aplicativo implantado funcione, mas também é provável que introduza dependências que você não precisa na produção.
Tenha cuidado ao adicionar novas dependências ao app. Cada um deles pode introduzir mais código sobre o qual você não tem controle completo. Como parte do pipeline de linting e testes regular, integre ferramentas que auditam seus arquivos de requisitos para determinar se você realmente usa ou importa suas dependências.
Alguns idiomas têm ferramentas para ajudar a gerenciar as dependências. Por exemplo, é possível usar o plug-in de dependência do Maven para analisar e gerenciar dependências Java.
Verificação de vulnerabilidades
Responder rapidamente a vulnerabilidades nas dependências ajuda a proteger sua cadeia de suprimentos de software.
A verificação de vulnerabilidades permite avaliar de forma automática e consistente se as dependências estão introduzindo vulnerabilidades no aplicativo. As ferramentas de verificação de vulnerabilidade consomem arquivos de bloqueio para determinar exatamente de quais artefatos você depende e notificar você quando novas vulnerabilidades surgirem, às vezes até com caminhos de upgrade sugeridos.
Por exemplo, o Artifact Analysis identifica vulnerabilidades do pacote do SO em imagens de contêiner. Ele pode verificar imagens quando são enviadas para o Artifact Registry e monitorá-las continuamente para encontrar novas vulnerabilidades por até 30 dias após o envio da imagem.
Você também pode usar a Verificação sob demanda para verificar imagens de contêineres localmente em busca de vulnerabilidades do SO, do Go e do Java. Isso permite identificar vulnerabilidades mais cedo para que você possa resolvê-las antes de armazená-las no Artifact Registry.
A seguir
- Saiba mais sobre os componentes da segurança da cadeia de suprimentos de software e como os serviços do Google Cloud ajudam a proteger seu software.
- Saiba mais sobre o Artifact Registry.
- Saiba mais sobre o Artifact Analysis e os tipos de verificação.