From a6cf46dadabfa7f76a645001d5158f818499de8e Mon Sep 17 00:00:00 2001 From: Chris Hostetter Date: Tue, 4 May 2021 10:20:59 -0700 Subject: [PATCH] LUCENE-9936: Add gpg signing of the tgz & zip distribution files --- help/gpgSigning.txt | 70 +++++++++++++++++++++++++++++++++++ lucene/packaging/build.gradle | 52 +++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 help/gpgSigning.txt diff --git a/help/gpgSigning.txt b/help/gpgSigning.txt new file mode 100644 index 00000000000..80e45f0e1d2 --- /dev/null +++ b/help/gpgSigning.txt @@ -0,0 +1,70 @@ +GPG Signing +=========== + +GPG Signing of distribution files (typically by a release manager) is done with the 'signDist' command. + +The only required configuration property Gradle needs is the 'signing.gnupg.keyName' (aka: the fingerprint) of +the key you wish to use: + +./gradlew signDist -Psigning.gnupg.keyName=YOUR_KEY_FINGERPRINT + +By default when you run this command, Gradle will delegate to the `gpg2` command for managing the signing of each file, which (should) +in turn use the `gpg-agent` to prompt you for your secret key only as needed using a dialog box specific to your operating system and/or +`gpg-agent` preferences. + +You may wish to put the `signing.gnupg.keyName` in your `~/.gradle/gradle.properties` so it is set automatically any time you use gradle + + +Additional Configuration +------------------------ + +The following additional properties -- specified either on the command line via `-P...` or in your `~/.gradle/gradle.properties` may be +useful/necessary in your system: + +signing.gnupg.useLegacyGpg=true # Changes the default executable from `gpg2` to `gpg` and explicitly sets `--use-agent` +signing.gnupg.executable=gpg # Allows explicit control over what command executable used (ex: `gpg2`, `gpg`, `gpg.exe`, etc...) +signing.gnupg.homeDir=/tmp/gnupg-home # overrides GnuPG's default home directory (ex: `~/.gnupg/`) +signing.gnupg.optionsFile=/tmp/gnupg-home/my.conf # overrides GnuPG's default configuration file +signing.gnupg.passphrase=... # Provide your passphrase to gradle to hand off to gpg. *NOT RECOMMENDED*, see below. + + +Notes About Error Messages +-------------------------- + + +### `gpg: signing failed: Inappropriate ioctl for device` + +This typically happens if your `gpg-agent` is configured (either globally for your operating system, or personally in your +`~/.gnupg/gpg-agent.conf`) to use a `pinentry` command which depends on using the same `tty` as the `gpg` command (ex: `pinentry-curses`, +or `pinentry-tty`, etc...). + +`tty` based `pinentry` implementations do not work when Gradle's `SigningPlugin` is attempting to invoke `gpg` -- among other problems: +Gradle is multi-threaded, and we sign multiple artifacts by default; so even if the `SigningPlugin` didn't automatically force `--no-tty` when +running `gpg` you could easily run into problems where a second `pinentry` process wanted to read from the same `tty` in the middle of you +typing in your passphrase to the first process. + +Developers are encouraged to configure a *non* `tty` based `pinentry` (ex: `pinentry-gnome`, `pinentry-x11`, `pinentry-qt`, `pinentry-mac`, +`pinentry-wsl-ps1`, etc...) either globally in your operating system, or personally in your `~/.gnupg/gpg-agent.conf`, or in a new +`gpg-agent.conf` file a new GnuPG configuration directory (containing a copy of your private keys) that you direct gradle to via +`signing.gnupg.homeDir` + +If none of these options are viable for you, then as a last resort you may wish to consider using the `signing.gnupg.passphrase=...` property. +This will expose your secret passphrase to the Gradle process, which will then pass it directly to each `gpg-agent` instance using +`--pinentry-mode=loopback`. + + +### `gpg: signing failed: No such file or directory` + +This may mean that there is a problem preventing `gpg` from communicating correctly with the `gpg-agent` (and/or invoking your `pinentry` +program) that is independent of gradle. Try running `pkill gpg-agent` and then retrying your `./gradlew` command + + +### `No value has been specified for property 'signatory.keyId'.` + +Do not bother ever attempting to set a command line (or gradle.properties) property named `signatory.keyId`. This is evidently the +name of an internal property that the gradle `SigningPlugin` expects the `GnupgSignatory` plugin we use to provide -- which it does +as long as you have specified a valid value for `signing.gnupg.keyName` + +If you see this error, it means you did not properly set `signing.gnupg.keyName` _AND_ you invoked a task which is attempting to use +the `SigningPlugin`, but does not depend on the custom `failUnlessGpgKeyProperty` to report the error correctly. Please file a Jira +noting what `./gradlew` command you attempted to run so we can fix it's dependencies, and try again after setting `signing.gnupg.keyName`. diff --git a/lucene/packaging/build.gradle b/lucene/packaging/build.gradle index 1c4aa3ecc27..e2698ad4744 100644 --- a/lucene/packaging/build.gradle +++ b/lucene/packaging/build.gradle @@ -20,6 +20,7 @@ plugins { id 'distribution' + id 'signing' } description = 'Lucene distribution packaging' @@ -167,4 +168,53 @@ configure(project(":lucene:luke")) { }) } } -} \ No newline at end of file +} + +configurations { + luceneTgz + luceneZip +} +artifacts { + luceneTgz(distTar) + luceneZip(distZip) +} + + + +// NOTE: we don't use the convinence DSL of the 'signing' extension to define our 'Sign' tasks because +// that (by default) adds our signature files to the 'archives' configuration -- which is what +// assemble & installDist try to copy/sync, so they wouldn't work w/o GPG installed (which would be bad). +// +// We also want to hook in our own property check dependency since the default error message from Sign task +// refers to the wrong (internal only) property name ("signatory.keyId") +signing { + useGpgCmd() // so gpg-agent can be used +} +task failUnlessGpgKeyProperty { + // placeholder that can be depended on by any task needing GPG key which will 'fail fast' if it's not set. + def propName = 'signing.gnupg.keyName' + + // This explicitly checks the taskGraph (instead of a simple 'doFirst') so it can fail the user's gradle + // invocation immediately before any unrelated build tasks may run in parallel + if ( ! project.hasProperty(propName) ) { + gradle.taskGraph.whenReady { graph -> + if ( graph.hasTask(failUnlessGpgKeyProperty) ) { + // TODO: it would be really nice if taskGraph was an actual graph and we could report what tasks in (transitive) depend on us + throw new GradleException("'$propName' property must be set for GPG signing, please see help/gpgSigning.txt") + } + } + } +} +task signDistTar(type: Sign) { + dependsOn failUnlessGpgKeyProperty + sign configurations.luceneTgz +} +task signDistZip(type: Sign) { + dependsOn failUnlessGpgKeyProperty + sign configurations.luceneZip +} +task signDist { + group = 'Distribution' + description = 'GPG Signs the main distributions' + dependsOn signDistTar, signDistZip +}