Skip to content

Commit

Permalink
feat: remove .sigstore.asc signatures if added by signing plugin
Browse files Browse the repository at this point in the history
By default, the plugin will remove .sigstore.asc.
Project property dev.sigstore.sign.remove.sigstore.asc=false
would keep .sigstore.asc files if they are needed

Closes #604

Signed-off-by: Vladimir Sitnikov <[email protected]>
  • Loading branch information
vlsi committed Jan 12, 2024
1 parent 38e59f4 commit bfd0cd6
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 12 deletions.
5 changes: 5 additions & 0 deletions sigstore-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ Automatically signs all Maven publications in Sigstore.
Provides `SigstoreSignFilesTask` task for signing files in Sigstore.
The plugin adds no tasks by default.

Properties:
* `dev.sigstore.sign.remove.sigstore.asc` (since 0.6.0, default: `true`). Removes `.sigstore.asc` files from the publication.
Sonatype OSSRH supports publishing `.sigstore` signatures, and it does not require `.sigstore.asc` files, so
`dev.sigstore.sign` plugin removes them by default. If you need to sign all the files, set this property to `false`.

Extensions:
* `sigstoreSign`: `dev.sigstore.sign.SigstoreSignExtension`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.the
import org.gradle.kotlin.dsl.withType
import org.gradle.plugins.signing.Sign
import kotlin.collections.set

abstract class SigstoreSignExtension(private val project: Project) {
Expand Down Expand Up @@ -78,6 +80,11 @@ abstract class SigstoreSignExtension(private val project: Project) {
this.signatureDirectory.set(signatureDirectory)
}

val removeSigstoreAsc =
project.findProperty("dev.sigstore.sign.remove.sigstore.asc")?.toString()?.toBoolean() != false

val publicationName = publication.name

val artifacts = mutableMapOf<PublicationArtifact, T>()
publication.allPublishableArtifacts {
val publishableArtifact = this
Expand All @@ -92,6 +99,20 @@ abstract class SigstoreSignExtension(private val project: Project) {
publishableArtifact,
DefaultDerivedArtifactFile(project.tasks.named<DefaultTask>(signTask.name), signatureLocation)
).apply { builtBy(signTask) }
// Gradle's signing plugin reacts on adding artifacts, and it might add .asc signature
// So we need to remove .sigstore.asc as it is unwanted in most of the cases
if (removeSigstoreAsc) {
project.tasks.withType<Sign>()
.matching { it.name.contains(publicationName, ignoreCase = true) }
.configureEach {
// Remove .sigstore.asc signature.
// Unfortunately, it will scan all the signatures every time,
// however, it seems to be the only way to do it since the artifacts can be added
// within afterEvaluate block, so we can't use afterEvaluate
// to "remove all .sigstore.asc" at once
signatures.removeIf { it.name.endsWith(".sigstore.asc") }
}
}
}
}
publication.whenPublishableArtifactRemoved {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright 2022 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package dev.sigstore.gradle

import dev.sigstore.testkit.BaseGradleTest
import dev.sigstore.testkit.TestedGradle
import dev.sigstore.testkit.TestedSigstoreJava
import dev.sigstore.testkit.annotations.EnabledIfOidcExists
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions
import org.gradle.util.GradleVersion
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.Arguments.arguments
import org.junit.jupiter.params.provider.MethodSource

@EnabledIfOidcExists
class RemoveSigstoreAscTest : BaseGradleTest() {
companion object {
@JvmStatic
fun signingSupportedGradleAndSigstoreJavaVersions(): Iterable<Arguments> =
if (isCI) {
gradleAndSigstoreJavaVersions()
} else {
// Find the first version that supports configuration cache for Gradle's signing plugin (8.1+)
listOf(
arguments(
TestedGradle(
// Signing plugin supports configuration cache since 8.1
gradleVersions().first { it >= GradleVersion.version("8.1") },
ConfigurationCache.ON
),
SIGSTORE_JAVA_CURRENT_VERSION
)
)
}

@JvmStatic
fun oneSigningSupportedGradleAndSigstoreJavaVersions(): Iterable<Arguments> =
signingSupportedGradleAndSigstoreJavaVersions().take(1)
}

@ParameterizedTest
@MethodSource("signingSupportedGradleAndSigstoreJavaVersions")
fun `basic configuration avoids signing sigstore with pgp`(gradle: TestedGradle, sigstoreJava: TestedSigstoreJava) {
prepareBuildScripts(gradle, sigstoreJava)

prepare(gradle.version, "publishAllPublicationsToTmpRepository", "-s")
.build()

assertSoftly {
assertSignatures("sigstore-test-1.0.pom")
assertSignatures("sigstore-test-1.0-sources.jar")
assertSignatures("sigstore-test-1.0.module")
assertSignatures("sigstore-test-1.0.pom")
}

if (gradle.configurationCache == ConfigurationCache.ON) {
val result = prepare(gradle.version, "publishAllPublicationsToTmpRepository", "-s")
.build()

assertThat(result.output)
.contains(
"Configuration cache entry reused",
"7 actionable tasks: 4 executed, 3 up-to-date",
)
}
}

@ParameterizedTest
@MethodSource("oneSigningSupportedGradleAndSigstoreJavaVersions")
fun `crossign sigstore with pgp`(gradle: TestedGradle, sigstoreJava: TestedSigstoreJava) {
prepareBuildScripts(gradle, sigstoreJava)
projectDir.resolve("gradle.properties").toFile().appendText(
"""
# By default, dev.sigstore.sign asks Gradle to avoid signing .sigstore as .sigstore.asc
# This is an opt-out hatch for those who need .sigstore.asc
dev.sigstore.sign.remove.sigstore.asc=false
""".trimIndent()
)
prepare(gradle.version, "publishAllPublicationsToTmpRepository", "-s")
.build()
assertSoftly {
assertSignatures("sigstore-test-1.0.pom", expectSigstoreAsc = true)
assertSignatures("sigstore-test-1.0-sources.jar", expectSigstoreAsc = true)
assertSignatures("sigstore-test-1.0.module", expectSigstoreAsc = true)
assertSignatures("sigstore-test-1.0.pom", expectSigstoreAsc = true)
}
}

private fun prepareBuildScripts(gradle: TestedGradle, sigstoreJava: TestedSigstoreJava) {
writeBuildGradle(
"""
plugins {
id("java")
id("signing")
id("maven-publish")
id("dev.sigstore.sign")
}
${declareRepositoryAndDependency(sigstoreJava)}
group = "dev.sigstore.test"
java {
withSourcesJar()
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'dev.sigstore.test'
artifactId = 'sigstore-test'
version = '1.0'
from components.java
}
}
repositories {
maven {
name = "tmp"
url = layout.buildDirectory.dir("tmp-repo")
}
}
}
signing {
useInMemoryPgpKeys(
'''$testOnlySigningKey''',
"testforsigstorejava"
)
sign(publishing.publications.withType(MavenPublication))
}
""".trimIndent()
)
writeSettingsGradle(
"""
rootProject.name = 'sigstore-test'
""".trimIndent()
)
if (gradle.version >= GradleVersion.version("8.1")) {
enableConfigurationCache(gradle)
}
}

private fun SoftAssertions.assertSignatures(name: String, expectSigstoreAsc: Boolean = false) {
assertThat(projectDir.resolve("build/tmp-repo/dev/sigstore/test/sigstore-test/1.0/$name.sigstore"))
.describedAs("$name should be signed with Sigstore")
.content()
.basicSigstoreStructure()
assertThat(projectDir.resolve("build/tmp-repo/dev/sigstore/test/sigstore-test/1.0/$name.asc"))
.describedAs("$name should be signed with PGP")
.isNotEmptyFile()
assertThat(projectDir.resolve("build/tmp-repo/dev/sigstore/test/sigstore-test/1.0/$name.asc.sigstore"))
.describedAs("$name.asc should NOT be signed with Sigstore")
.doesNotExist()
assertThat(projectDir.resolve("build/tmp-repo/dev/sigstore/test/sigstore-test/1.0/$name.sigstore.asc"))
.apply {
if (expectSigstoreAsc) {
describedAs("$name.sigstore should be signed with PGP")
exists()
} else {
// We don't want to sign .sigstore files with PGP
describedAs("$name.sigstore should NOT be signed with PGP")
doesNotExist()
}
}
}

private val testOnlySigningKey = """
-----BEGIN PGP PRIVATE KEY BLOCK-----
lIYEZaDRyxYJKwYBBAHaRw8BAQdAjMi3g07livoPo+se6/+wF7LRv2DDJ6UKVBrp
9rugpwj+BwMCDZlNm7zWHTP6ny1jqI5sdTFaEkHRjFhm63Il9qeF7QcSibgAnBO5
YK0E4vp8MUQxSAwoOV80mO46a2Ci9hA281lXH6fFTP3qyERXl2/ilrQvVGVzdCBL
ZXkgZm9yIFNpZ3N0b3JlIEphdmEgPHNpZ3N0b3JlQGdpdGh1Yi5pbz6IkwQTFgoA
OxYhBNejX8GGaAn2Jspav54UgcovliH1BQJloNHLAhsDBQsJCAcCAiICBhUKCQgL
AgQWAgMBAh4HAheAAAoJEJ4UgcovliH1YDUBAPE1yBo7i4YgHuHKIGLqkOJqEKE5
Jbw8ffyZO6tqud2qAP49liajq/HkdEXgUdA6DySpzLYFtd+F6UlpTQE0TeaLAA==
=6fgq
-----END PGP PRIVATE KEY BLOCK-----
""".trimIndent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package dev.sigstore.testkit

import org.assertj.core.api.AbstractCharSequenceAssert
import org.assertj.core.api.SoftAssertions
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.internal.DefaultGradleRunner
import org.gradle.util.GradleVersion
Expand Down Expand Up @@ -46,23 +47,33 @@ open class BaseGradleTest {
System.getProperty("sigstore.test.current.version")
)

@JvmStatic
fun gradleVersions() = listOf(
// Gradle 7.2 fails with "No service of type ObjectFactory available in default services"
// So we require Gradle 7.3+
"7.3",
"7.5.1",
"8.1",
"8.5",
).map { GradleVersion.version(it) }

@JvmStatic
fun gradleVersionAndSettings(): Iterable<Arguments> {
if (!isCI) {
// Make the test faster, and skip extra tests with the configuration cache to reduce OIDC flows
// Gradle 7.2 fails with "No service of type ObjectFactory available in default services"
return listOf(arguments(TestedGradle("7.3", ConfigurationCache.ON)))
// Execute a single combination only when running locally
return listOf(arguments(TestedGradle(gradleVersions().first(), ConfigurationCache.ON)))
}
return mutableListOf<Arguments>().apply {
add(arguments(TestedGradle("7.3", ConfigurationCache.ON)))
add(arguments(TestedGradle("7.5.1", ConfigurationCache.ON)))
add(arguments(TestedGradle("7.5.1", ConfigurationCache.OFF)))
return buildList {
addAll(
gradleVersions().map { arguments(TestedGradle(it, ConfigurationCache.ON)) }
)
add(arguments(TestedGradle(gradleVersions().first(), ConfigurationCache.OFF)))
}
}

@JvmStatic
fun sigstoreJavaVersions(): Iterable<Arguments> {
return mutableListOf<Arguments>().apply {
return buildList {
add(arguments(SIGSTORE_JAVA_CURRENT_VERSION))
// For now, we test the plugins only with locally-built sigstore-java version
if (isCI && false) {
Expand Down Expand Up @@ -150,9 +161,9 @@ open class BaseGradleTest {
)
}

protected fun prepare(gradleVersion: String, vararg arguments: String) =
protected fun prepare(gradleVersion: GradleVersion, vararg arguments: String) =
gradleRunner
.withGradleVersion(gradleVersion)
.withGradleVersion(gradleVersion.version)
.withProjectDir(projectDir.toFile())
.apply {
this as DefaultGradleRunner
Expand All @@ -176,7 +187,7 @@ open class BaseGradleTest {
if (gradle.configurationCache != ConfigurationCache.ON) {
return
}
if (GradleVersion.version(gradle.version) < GradleVersion.version("7.0")) {
if (gradle.version < GradleVersion.version("7.0")) {
Assertions.fail<Unit>("Gradle version $gradle does not support configuration cache")
}
// Gradle 6.5 expects values ON, OFF, WARN, so we add the option for 7.0 only
Expand All @@ -189,6 +200,9 @@ open class BaseGradleTest {
)
}

protected fun assertSoftly(body: SoftAssertions.() -> Unit) =
SoftAssertions.assertSoftly(body)

protected fun <SELF : AbstractCharSequenceAssert<SELF, ACTUAL>, ACTUAL : CharSequence> AbstractCharSequenceAssert<SELF, ACTUAL>.basicSigstoreStructure() =
contains(
""""mediaType": "application/vnd.dev.sigstore.bundle+json;version\u003d0.2"""",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
*/
package dev.sigstore.testkit

import org.gradle.util.GradleVersion

/**
* Lists Gradle versions and its configuration for backward compatibility testing of Sigstore Gradle plugin.
*/
data class TestedGradle(
val version: String,
val version: GradleVersion,
val configurationCache: BaseGradleTest.ConfigurationCache
)

0 comments on commit bfd0cd6

Please sign in to comment.