5

Improved security testing for git-based Gradle projects using lockfile

 3 years ago
source link: https://snyk.io/blog/security-testing-git-based-gradle-projects-lockfile/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Improved security testing for git-based Gradle projects using lockfile

Antonio Gomes

December 7, 2020

Over the past year, we have been working hard to improve our testing for Gradle projects imported from Git repositories by making it more reliable, accurate, and scalable. 

We understood that parsing a Gradle manifest, instead of a Gradle lock file, would be a never-ending war that we would always lose. Trying to interpret the Gradle complexity model by parsing a build .gradle file is a non-deterministic approach, that relies on many assumptions. Configurations that strictly force a certain version or a maximum version of one or multiple dependencies, or whose versions are defined in other files, would not always be considered. Also, this approach will never explicitly declare what Gradle is going to resolve and use in different configuration contexts, such as compile, test, or runtime phases. All of these can lead to inaccurate or incomplete results.

That’s why different dependency tools rely on dependency locking to help developers achieve reproducible builds. Examples include npm (package-lock.json), yarn (yarn.lock), RubyGems (Gemfile.lock), and now also Gradle with gradle.lockfile.

How to generate a Gradle lockfile

This is how a typical gradle.lockfile looks: 

juCjGBsh5cnFUzPgylzp8O8Hs5wiyTnCWvddBh6MiIhloQaNO81Ad3_y6cEGBBToAXWHkKwVGQDfFEcSdZAUU8hPdwysCoHdVuCwI5Zg3r8g15B8stSGkp6JPRwNBX1kED42Ms_S

It does not contain a tree or graph structure—instead, does its job by including all the direct and transitive dependencies being used, specifying the versions and the configurations that they belong to.

To generate a lockfile in your single project or mono-repo (containing different sub-projects) edit your build.gradle with the following instructions:

VPQVtnxd0hPlCWgDa7CqpSP7-rU6hBFyO46gexv44q4lEowi5Nj6ynfKPVVSD7UzLZ0anGXOjB9uuz7pNHJ-XDr_f8XAPDeNiNe8WYC0noz3e70Yq9Gf5_VrX2D84OHc-zpXI_V1

As you can see we are asking Gradle to trigger dependencyLocking considering all available configurations—e.g. annotationProcessor, compile, runtime etc.

Once we have added the code, it will allow Gradle to understand what our intentions are.

In this example, we have a single configuration but usually, we can have multiple configurations and we could end up with multiple lockfiles per configuration. In order to have a single source of truth let’s see how we can generate a single lockfile per project. 

Generating a single lockfile

Using a feature preview of Gradle 7.0 that is already available for Gradle 6.0+ users, we can generate a single gradle.lockfile per project that contains all the different lock states for different configurations. This single lockfile approach will be the default from Gradle 7.0.

b268CB04iXBR64Ew29lBS62i-CHORHyNwz7XVE7TdVcOQVkP5sfTDYXBHfkDeR7sIE3A2VUfnJuz6Kr9fqbYKwPqj_D_tCU0o11MuZ6aYRS6dhCF6Qz9iZFTOQhFD-fKV-6or7U4

Once we define this rule the next step is run in our terminal the command gradle resolveAndLockAll --write-locks that will generate the lockfile below: 

rbAqekiG149RtawbMUwrvLJG-IbcSgD6Kva3TaB5TwuUOGPiE1YQlQM7WwOKNuVfl56CzAQd5SPn5Zz35zq4jz0r-_p5vhnvC2WjkDz75i7-otajV0xwPtPn4urshv5dD-eBVdyM

From the above we can see that:

  • each line still represents a single dependency in the GAV notation group:artifact:version.
  • every dependency has a list of configurations where the dependency belongs.
  • the last line empty contains the list of available configurations that do not have dependencies.

How Gradle lockfile makes your life easier

Using dependency locking can prevent unexpected errors introduced by a transitive dependency that you have no control over when using dynamic dependency versions (e.g. 1.+ or [1.0,2.0). This is a common scenario that can happen anytime behind the scenes.

Here, we have a small build.gradle file where we declare a dependency with a dynamic version:

siFSU6UPfO4V52eLocAhjROhqhn0zcyalNVtFY8LLm-Q545KxeAX0HX7thtOaIo9KmO0cbFzCxn5FBL5CzhWGD0DZVBDx9sL-dA3qk7CqdgCNsE_lmNOwRVXq8mNNnGmr_XlZvRN

By running the command gradle -q dependencies, the output is not 2.11 or 2.14. As we can see from the image below, Gradle resolved the dependencies to 2.13.

G_asIiWDjOq1DlGBz8SocPr4Kh6iqyOckbaniIADL7FgA0GIo6HZ4w_aJ31hRQIam4wZh8pfkuSyfKTcPiYiyOxBrurbCqHUtPshifYuxKhpttpUrPzG85QKY2tMkUVuIvm9k57s

Now, by verifying the dependencies available in the lockfile, we can confirm that the command output matches with the lockfile state of the application of our example.

Ch1jOzfPlvk4GLWlHSopcBys8jappxFf8V8sH7UHNzPAU3Nf5nY0Jb2zjf-NGfhuvj1MpftXpCslQetVLNYIasbR4wx_uWa7KJRlJA0YEbcmn7wyYfCr3xiyEwi_2vtU8Tmoa54b

Now it’s time to see the results!

Here, we have a scenario where we failed to parse the build.gradle file that contained dependencies with dynamic versions. Let’s compare the results by utilizing a gradle.lockfile.

fIdloQa3L0zCd5BbGRqB4oQrCdpKfp9Nu_cSLf5VoVYR4iHDQ1rait4QgWp5esFJJwUfp7lKfQNR3IC0JhIKKdaHibLnmytbrQdgHLyZU4VUy-J6XY0Qs5sEELxXL0CF0CzqsKVu

As we can see the results when utilising the gradle.lockfile are really superior—we have been able to identify 24 dependencies vs 0 from the previous case, including direct and transitive ones, and report a total of 33 issues!

OVBoMbrBpOdxc1wTEzBblkwWrB1pl6Pa9xYxMezX3Epq0rfGMv4auJp9mBQIO77CW14KLbGy1B1miv5VKNksddj45LpG77IgbwEpu65MK7C76GSG9ihcntJyGYs6-FW6z7t3ER9E

If you wish to dive in dependency locking in Gradle further, check out the official documentation.

We hope you like this Snyk feature! If you don’t have a Snyk account yet, you can get one for free here


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK