Skip to main content

Testes de segurança aprimorados para projetos Gradle baseados em Git usando arquivos de lock

Escrito por

Antonio Gomes

Blog illustrations Gradle

7 de dezembro de 2020

0 minutos de leitura

No último ano, trabalhamos bastante para tornar os testes de segurança de projetos Gradle importados de repositórios Git mais confiáveis, precisos e escaláveis.

Entendemos que analisar um manifesto do Gradle, em vez de um arquivo de lock do Gradle, seria uma batalha sem fim que nunca venceríamos. Tentar interpretar o modelo de complexidade do Gradle analisando um arquivo build.gradle é uma abordagem não determinística, que depende de muitas suposições. Configurações que forçam estritamente uma versão específica ou uma versão máxima de uma ou mais dependências, ou cujas versões são definidas em outros arquivos, nem sempre seriam consideradas. Além disso, essa abordagem nunca declarará explicitamente o que o Gradle resolverá e usará em diferentes contextos de configuração, como nas fases de compilação, teste ou execução. Tudo isso pode levar a resultados imprecisos ou incompletos.

Por isso, diferentes ferramentas de dependências usam o bloqueio de dependências para ajudar desenvolvedores a obter compilações reproduzíveis. Alguns exemplos são npm (package-lock.json), yarn (yarn.lock), RubyGems (Gemfile.lock) e, agora, também o Gradle com gradle.lockfile.

Como gerar um arquivo de lock do Gradle

Este é um exemplo típico de arquivo gradle.lockfile:

Arquivo de lock de dependências do Gradle com avisos sobre arquivos gerados e entradas bloqueadas das versões 2.12.1 da API e do core do Log4j.

Ele não contém uma estrutura em árvore ou em grafo. Em vez disso, faz seu trabalho incluindo todas as dependências diretas e transitivas em uso, e especificando as versões e as configurações às quais elas pertencem.

Para gerar um arquivo de lock em um projeto único ou em um monorepo (com diferentes subprojetos), edite o arquivo build.gradle com as instruções a seguir:

Editor build.gradle com tema escuro exibindo a configuração de bloqueio de dependências e uma tarefa para resolver e gravar os bloqueios de dependências

Como você pode ver, estamos pedindo ao Gradle para ativar dependencyLocking considerando todas as configurações disponíveis, como annotationProcessor, compilação, execução etc.

Depois de adicionar o código, o Gradle entenderá o que queremos fazer.

Neste exemplo, temos uma única configuração, mas normalmente podemos ter várias configurações e acabar com vários arquivos de lock, um por configuração. Para ter uma única fonte da verdade, veja como gerar um único arquivo de lock por projeto.

Gerar um único arquivo de lock

Com uma prévia de recurso do Gradle 7.0, já disponível para quem usa o Gradle 6.0 ou superior, podemos gerar um único gradle.lockfile por projeto, contendo todos os diferentes estados de lock para cada configuração. Essa abordagem com um único arquivo de lock será o padrão a partir do Gradle 7.0.

Editor de código exibindo o arquivo settings.gradle com a prévia do recurso ONE_LOCKFILE_PER_PROJECT do Gradle ativada.

Depois de definir essa regra, o próximo passo é executar no terminal o comando gradle resolveAndLockAll --write-locks, que gerará o arquivo de lock abaixo:

O Visual Studio Code mostra um projeto Gradle e o arquivo gradle.lockfile com entradas de dependências, além do comando `gradle resolveAndLockAll --write-locks`.

Como podemos ver acima:

  • cada linha ainda representa uma única dependência na notação GAV group:artifact:version.

  • cada dependência tem uma lista das configurações às quais pertence.

  • a última linha, empty, contém a lista das configurações disponíveis que não têm dependências.

Como o arquivo de lock do Gradle facilita sua vida

O bloqueio de dependências pode evitar erros inesperados causados por uma dependência transitiva sobre a qual você não tem controle quando usa versões dinâmicas de dependências (por exemplo, 1.+ ou [1.0,2.0). Esse cenário é comum e pode acontecer a qualquer momento sem que você perceba.

Aqui temos um pequeno arquivo build.gradle que declara uma dependência com uma versão dinâmica:

Editor de código com tema escuro exibindo um arquivo de build do Gradle com Java, Maven Central e uma dependência de processador de anotações do Log4j.

Ao executar o comando gradle -q dependencies, o resultado não é 2.11 nem 2.14. Como podemos ver na imagem abaixo, o Gradle resolveu as dependências para a versão 2.13.

Saída do terminal mostrando a resolução de dependências do Gradle para a configuração annotationProcessor, incluindo Log4j core e API na versão 2.13.3.

Agora, ao verificar as dependências disponíveis no arquivo de lock, podemos confirmar que o resultado do comando corresponde ao estado do arquivo de lock da aplicação deste exemplo.

Captura de tela de um aviso no arquivo de lock de dependências do Gradle informando que edições manuais podem interromper a compilação, seguido por entradas de dependências do Log4j.

Agora é hora de ver os resultados!

Neste caso, não conseguimos analisar o arquivo build.gradle, que continha dependências com versões dinâmicas. Vamos comparar os resultados usando um gradle.lockfile.

Painel do projeto no Snyk mostrando o arquivo build.gradle do unparsed-gradle-repo e a guia Dependências

Como podemos ver, os resultados com o gradle.lockfile são muito melhores: conseguimos identificar 24 dependências, contra 0 no caso anterior, incluindo dependências diretas e transitivas, e reportar um total de 33 problemas!

Visualização de dependências do projeto build.gradle, exibindo os testes do arquivo de bloqueio do Gradle e vulnerabilidades e problemas de licença nos pacotes listados

Se quiser saber mais sobre o bloqueio de dependências no Gradle, consulte a documentação oficial.

Esperamos que você goste desse recurso do Snyk! Ainda não tem uma conta Snyk? Cadastre-se gratuitamente.

Comece a participar de desafios de Capture the Flag

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