Gerenciamento de dependências

Este documento descreve as dependências do aplicativo e as práticas recomendadas para gerenciá-las, incluindo monitoramento de vulnerabilidades, verificação de artefatos e etapas para reduzir sua abrangência de dependências e oferecer compatibilidade com versões reproduzíveis.

Dependências de aplicativo são tecnologias necessárias para criar ou executar um aplicativo. Alguns exemplos de dependências de software incluem sistemas operacionais, bibliotecas de software, plug-ins, bancos de dados ou plataformas.

As dependências podem incluir componentes criados por você e softwares de terceiros. A abordagem do gerenciamento de dependências pode afetar a segurança e a confiabilidade dos aplicativos.

As especificações 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.

Abordagens para incluir dependências

Há vários métodos comuns para incluir dependências com seu aplicativo:

Instalar diretamente de fontes públicas
Instale as dependências de código aberto diretamente de repositórios públicos, como o Docker Hub, npm, PyPI ou Maven Central. Essa abordagem é conveniente porque você não precisa manter as dependências externas. No entanto, como você não controla essas dependências externas, a cadeia de suprimentos de software está 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 fornecedores. Em vez de instalar uma dependência externa de um repositório público durante suas versões, faça o download dela e copie-a para a árvore de origem do projeto. Você tem mais controle sobre as dependências usadas e usadas, mas há várias desvantagens:
  • As dependências fornecidas aumentam o tamanho do repositório de origem e apresentam mais desligamento de usuários.
  • É necessário fornecer as mesmas dependências em cada aplicativo separado. Se o repositório de origem ou o processo de compilação não forem compatíveis com módulos de origem reutilizáveis, talvez seja necessário manter várias cópias das dependências.
  • O upgrade de dependências fornecidas pode ser mais difícil.
Armazenar dependências em um registro particular
O Artifact Registry oferece a conveniência de instalar a partir de um repositório público, além de controlar suas dependências.
  • É possível configurar os clientes do pacote de linguagem e do Docker para interagir com repositórios particulares no Artifact Registry, da mesma forma que fazem com repositórios públicos.
  • Você controla as dependências nos repositórios particulares e pode restringir o acesso a cada um deles.
  • As dependências são centralizadas para todos os aplicativos.
  • Os repositórios estão totalmente integrados ao Cloud Build e aos ambientes de execução do Google Cloud, como o Google Kubernetes Engine e o Cloud Run.
  • Aproveite o fluxo de trabalho de gerenciamento de metadados, verificação de vulnerabilidades e aprovação de implantação usando o Container Analysis e a autorização binária.

Quando possível, use um registro particular para as dependências. Em situações em que não é possível usar um registro particular, considere o fornecedor das dependências para ter controle sobre o conteúdo da cadeia de suprimentos de software.

Fixação de versão

A fixação de versões significa restringir uma dependência do aplicativo a uma versão ou intervalo de versão específico. O ideal é fixar uma única versão de uma dependência.

Fixar a versão de uma dependência ajuda a garantir que as versões do aplicativo sejam reproduzíveis. No entanto, isso também significa que as builds não incluem atualizações para a dependência, incluindo correções de segurança, correções de bugs ou melhorias.

É possível atenuar esse problema usando ferramentas de gerenciamento de dependências automatizadas que monitoram dependências nos repositórios de origem para novas versões. Essas ferramentas fazem atualizações nos seus arquivos de requisitos para fazer upgrade de dependências conforme necessário, muitas vezes incluindo informações do registro de alterações ou outros detalhes.

A fixação de versões se aplica somente a dependências diretas, não a transitivas. Por exemplo, se você fixar a versão do pacote my-library, o alfinete restringirá a versão de my-library, mas não restringirá as versões do software que my-library tem uma 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 atua como um identificador exclusivo. Você pode 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 hash ajuda a identificar a substituição, adulteração ou corrupção de dependências, por meio de um ataque de interceptação ou um comprometimento do repositório de artefatos.

O uso da verificação hash requer a confiança de que o hash recebido do repositório de artefatos no momento da verificação (ou no momento da primeira recuperação) não está comprometido.

Verificação de assinatura

A verificação de assinatura aumenta a segurança do processo. O repositório de artefatos, os mantenedores do software ou ambos podem assinar artefatos. Serviços como o sigstore fornecem uma maneira para os mantenedores assinarem artefatos de software e para os consumidores verificarem essas assinaturas.

Bloquear arquivos 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 será instalada em um aplicativo. Geralmente produzidos automaticamente pelas ferramentas de instalação, os arquivos de bloqueio combinam fixação de versões e verificação de assinatura ou hash com uma árvore de dependência completa do aplicativo.

As ferramentas de instalação criam árvores de dependência ao resolver totalmente todas as dependências transitivas downstream das suas dependências de nível superior. Em seguida, elas incluem a árvore de dependências no seu arquivo de bloqueio. Como resultado, apenas essas dependências podem ser instaladas, tornando as versões mais reproduzíveis e consistentes.

Como combinar dependências particulares e públicas

Aplicativos nativos da nuvem modernos geralmente dependem de código aberto de terceiros, bem como de bibliotecas internas de código fechado. O Artifact Registry permite compartilhar a lógica de negócios em vários aplicativos e reutilizar as mesmas ferramentas 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 ataques de confusão de dependência. Ao publicar projetos com o mesmo nome do seu projeto interno em repositórios de código aberto, os invasores podem tirar proveito dos instaladores mal configurados para instalar o código malicioso em vez da dependência interna.

Para evitar um ataque de confusão de dependências, é necessário seguir várias etapas:

  • Inclua assinaturas em um arquivo de bloqueio para verificar a assinatura ou os hashes
  • Separar a instalação de dependências de terceiros e internas em duas etapas distintas
  • Espelhe explicitamente as dependências de terceiros necessárias no seu repositório particular, manualmente ou com um proxy de pull
  • Para desenvolvimento baseado em contêiner, use fontes confiáveis para suas imagens de base. O Google fornece imagens de base gerenciadas que podem ser usadas diretamente e um pipeline de imagem seguro para gerar as próprias imagens de base.

Como remover dependências não usadas

your medida que suas necessidades mudam e seu aplicativo evolui, é possível alterar ou parar de usar algumas das dependências. Continuar instalando dependências não utilizadas com seu aplicativo aumenta sua abrangência de dependência e aumenta o risco de você ser comprometido por uma vulnerabilidade nessas dependências.

Depois que o aplicativo funciona 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, implante 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 desnecessárias na produção.

Tenha cuidado ao adicionar novas dependências ao aplicativo. Cada uma delas tem o potencial de introduzir mais códigos sobre os quais você não tem controle total. Como parte do pipeline regular de inspeção e teste, integre ferramentas que auditem os arquivos de requisitos para determinar se você realmente usa ou importa suas dependências.

Verificação de vulnerabilidades

Responder rapidamente às vulnerabilidades nas suas dependências ajuda a proteger a cadeia de suprimentos de software.

A verificação de vulnerabilidades permite avaliar de maneira automática e consistente se as dependências estão introduzindo vulnerabilidades no aplicativo. As ferramentas de verificação de vulnerabilidades consomem arquivos de bloqueio para determinar exatamente os artefatos de que você depende e notificam você quando novas vulnerabilidades aparecem, às vezes até mesmo com caminhos de upgrade sugeridos.

Ferramentas como o Container Analysis podem fornecer uma ampla variedade de verificações de vulnerabilidade para imagens de contêiner, bem como artefatos de linguagem, como a verificação de pacotes Java. Quando ativado, esse recurso identifica as vulnerabilidades do pacote nas imagens do contêiner. As imagens são verificadas quando são carregadas no Artifact Registry, e os dados são monitorados continuamente para encontrar novas vulnerabilidades por até 30 dias após o envio.

Também é possível usar a verificação sob demanda para verificar imagens de contêiner localmente. Isso permite identificar vulnerabilidades antecipadamente para que você possa resolvê-las antes de armazená-las no Artifact Registry.