ben-manes / gradle-versions-plugin

Gradle plugin to discover dependency updates
Apache License 2.0
3.87k stars 200 forks source link

Add verification for Gradle Wrapper JAR version #448

Open Vampire opened 3 years ago

Vampire commented 3 years ago

It would be nice if the Gradle wrapper JAR version would also be verified. It is not necessary to check for "latest", but it would imho be sufficient to check for it being from the current Gradle version. If you e. g. update Gradle versions by simply editing the gradle-wrapper.properties file, you never get an update for the actual wrapper files. And if you use the wrapper task once per update, you are always potentially one wrapper behind, as the wrapper files are generate by the old version. You always have to remember to call the wrapper task twice or first edit the properties manually and then call the wrapper task to also have the latest wrapper files present.

It would be nice if this plugin would also verify the wrapper JAR version and show it as outdated if it does not match. A simple manual implementation of this check works like this:

val validateGradleWrapperJar by tasks.registering {
    onlyIf {
        !gradle.startParameter.isOffline
    }

    doLast {
        val expectedDigest = URL("https://services.gradle.org/distributions/gradle-${gradle.gradleVersion}-wrapper.jar.sha256").readText()

        val sha256 = MessageDigest.getInstance("SHA-256")
        layout
                .projectDirectory
                .dir("gradle")
                .dir("wrapper")
                .file("gradle-wrapper.jar")
                .asFile
                .inputStream()
                .let { DigestInputStream(it, sha256) }
                .use { it.copyTo(NullOutputStream()) }
        val actualDigest = sha256.digest().let {
            "%02x".repeat(it.size).format(*it.toTypedArray())
        }

        check(expectedDigest == actualDigest) {
            "The wrapper JAR does not match the configured Gradle version, please update the wrapper"
        }
    }
}

class NullOutputStream : OutputStream() {
    override fun write(b: Int) {
    }
}
ben-manes commented 3 years ago

Is updating the wrapper beneficial? Most of the time the jar version doesn't matter, with a few cases where Gradle recommended updates in their release notes. In my projects I update the properties file directly.

The main concern that I'm aware of is man-in-the-middle attacks by checking in an unauthentic jar. The gradle/wrapper-validation-action is a solution to that problem by using the sha256 hash. However it doesn't validate version matching like you do.

Vampire commented 3 years ago

Imho it is beneficial, yes. It is as beneficial as any other library or tool update. It might fix bugs, it might improve usage (like showing download progress it didn't before), it might improve performance, ....

The wrapper does not change between each version since Gradle 5.1, so a wrapper JAR update is also not always necessary. But sometimes it is necessary even with the release notes not really mentioning it. For example in Gradle 6.6 there was a bug fixed that caused a NumberFormatException with Java 15, but only the list of fixed bugs mentions it, not the release notes.

In most cases I also just edit the properties file directly, which is exactly the problem. I want to be notified, that I should also have updated the Gradle wrapper JAR. And actually even if one would do gradlew wrapper --gradle-version 6.8.3 the wrapper files would not have been updated to the version from 6.8.3 but to the version that was used to run the wrapper task. So you either need to edit the properties file first and then run gradlew wrapper or run gradlew wrapper --gradle-version 6.8.3 && gradlew wrapper to properly update the wrapper files too.

jaredsburrows commented 2 years ago

This plug detects potential updates for dependencies but does not update them. Adding support to verify the Gradle wrapper itself might suggest we would need/want to add support for verifying dependencies from their respective repositories.

Vampire commented 2 years ago

This ticket is about verifying the version, so should fit perfectly well in the plugin concept that also checks for updates of Gradle itself. It should inform you that there is a newer wrapper jar for the Gradle version you are running. That my example uses the SHA-256 checksum for that is just an implementation detail to determine whether it is the correct one or not, just as a means to determine the version, not to verify its not corrupted or something.

As described I also wouldn't check in this case that the wrapper is the latest one, just that it is the one matching your Gradle version. It is very unlikely that one has a wrapper jar of a newer Gradle version, so if the SHA-256 of the jar does not match the one for the current version, it is highly likely that the wrapper jar is outdated and should be updated.

ben-manes commented 2 years ago

Would the following be what you'd like to see added under Gradle updates?

Gradle updates:
 - Gradle: [7.4 -> 7.4.2 -> 7.5-rc-1]
 - Wrapper: [7.0 -> 7.4 -> 7.4.2 -> 7.5-rc-1]
Vampire commented 2 years ago

That would be one option, but I personally would already be happy if it reported that the wrapper version does not match the current Gradle version, as that just needs checking one remote checksum file like in my manual example. But you can of course also parse for example the https://gradle.org/release-checksums/ site to have all possible checksums in one go as long as the format doesn't change, or you can of course get https://services.gradle.org/versions/all and then get all the referenced checksum files additionally to build up a checksum / version mapping.

ben-manes commented 2 years ago

Seems that the gradle/wrapper-validation-action makes a call per version for that checksum. I'm surprised when they needed it they relied on this O(n) approach.

Vampire commented 2 years ago

Yep, that's the last variant I wrote. So it seems there is really no machine-readable uri with all checksums.