Commit a02a2c95 authored by Jason Plum's avatar Jason Plum
Browse files

Add Debian (.deb) package signing (implementing debsig)

See:

- https://gitlab.com/gitlab-org/omnibus/merge_requests/7
- https://github.com/chef/omnibus/issues/402

Add Debian package signing, via methodology describe in `debsigs` documentation
https://gitlab.com/debsigs/debsigs

Addition of a `sign_deb_file` function to `Packager::DEB`, after `create_deb_file`. The essential concepts of what is required to sign a `.deb` with with a `type: origin` signature is delineated per the link to `debsigs` above. There is no current functionailty built into `dpkg` scripting akin to `rpm --addsign`. Since the `.deb` file format is simple, we extract the contents of the archvice (`ar x`), sign the concatenated (specifically ordered) contents, and then append the created signature to the archive (`ar rc debfile _gpgorigin`).

These steps could have been accomplished in pure Ruby with the addition of several modules (GPGME, libarchive) except for two concerns: age & maintenance, `fakeroot` requirements.

Tests have been added to attempt to cover the behavior correctly.

- `gpg` : This is already an existing requirement of `Packager::RPM` due to the use of `rpmsign`
- `ar` : Most systems that attempt to build Debian packages will have the `ar` command, and it has been confirmed that MacOS also has this utility.
- `fakeroot` : This is inline with `ar`, however it should be noted that not all systems had this program, and as such it has been added to the Omnibus cookbook.

Adding `ar` and `fakeroot` to the required tools compiled by Omnibus for ensuring this presence was done by @kwilczynski in #217

There is *no need* to add `debsigs`/`debsig-verify` as a requirement, as we are implmenting the login in Ruby and `Shellout`.

Care was taken to ensure compatibility with distribution provided binary versions of `gpg2` or `gpg` for LTS versions of distributions supported by GitLab. This list can be seen at https://gitlab.com/gitlab-org/omnibus/merge_requests/7#note_35053215 . The code is written to prefer `gpg2` if present.

GitLab experienced issues in regards to `gpg --import`, and eventually settled on `gpg --batch --no-tty --allow-secret-key-import --import` as a part of our CI job. So long as the key is present for the GPG calls (via `--homedir #{ENV['HOME']}/.gnupg`) this should not be an issue for any non-automated use. Note that this behavior is the same with RPM signing behaviors. As the GPG key import process is not a part of Omnibus itself, this should be of little concern to this code changes in this MR.

Relates to https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2537
Relates to https://github.com/chef-cookbooks/omnibus/pull/217
Closes https://github.com/chef/omnibus/issues/402



Submitted with the approval of GitLab, and with great thanks for the project!
Signed-off-by: Jason Plum's avatarJason Plum <jplum@gitlab.com>
parent 52393d7c
......@@ -39,6 +39,7 @@ package :deb do
license 'Apache 2.0'
priority 'extra'
section 'databases'
signing_passphrase 'acbd1234'
end
```
......@@ -46,6 +47,7 @@ Some DSL methods available include:
| DSL Method | Description |
| :------------------: | --------------------------------------------|
| `signing_passphrase` | The passphrase to sign the RPM with |
| `vendor` | The name of the package producer |
| `license` | The default license for the package |
| `priority` | The priority for the package |
......@@ -56,4 +58,4 @@ If you are unfamiliar with any of these terms, you should just accept the defaul
For more information, please see the [`Packager::DEB` documentation](http://www.rubydoc.info/github/chef/omnibus/Omnibus/Packager/DEB).
### Notes on DEB-signing
At this time, signing Debian packages is not supported.
To sign an DEB, you will need a GPG keypair. You can [create your own signing key](http://www.madboa.com/geek/gpg-quickstart/) or [import an existing one](http://irtfweb.ifa.hawaii.edu/~lockhart/gpg/gpg-cs.html). Omnibus will automatically call `gpg` with arguments that assume the real name associated to the GPG key is the same as the name of the project maintainer as specified in your Omnibus config.
......@@ -60,12 +60,37 @@ module Omnibus
# Create the deb
create_deb_file
# Sign the deb
sign_deb_file
end
#
# @!group DSL methods
# --------------------------------------------------
#
# Set or return the signing passphrase. If this value is provided,
# Omnibus will attempt to sign the DEB.
#
# @example
# signing_passphrase "foo"
#
# @param [String] val
# the passphrase to use when signing the DEB
#
# @return [String]
# the DEB-signing passphrase
#
def signing_passphrase(val = NULL)
if null?(val)
@signing_passphrase
else
@signing_passphrase = val
end
end
expose :signing_passphrase
#
# Set or return the vendor who made this package.
#
......@@ -389,6 +414,62 @@ module Omnibus
end
end
#
# Sign the +.deb+ file with gpg. This has to be done as separate steps
# from creating the +.deb+ file. See +debsigs+ source for behavior
# replicated here. +https://gitlab.com/debsigs/debsigs/blob/master/debsigs.txt#L103-124+
#
# @return [void]
def sign_deb_file
if !signing_passphrase
log.info(log_key) { "Signing not enabled for .deb file" }
return
end
log.info(log_key) { "Signing enabled for .deb file" }
# Check our dependencies and determine command for GnuPG. +Omnibus.which+ returns the path, or nil.
gpg = nil
if Omnibus.which("gpg2")
gpg = "gpg2"
elsif Omnibus.which("gpg")
gpg = "gpg"
end
if gpg && Omnibus.which("ar")
# Create a directory that will be cleaned when we leave the block
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir) do
# Extract the deb file contents
shellout!("ar x #{Config.package_dir}/#{package_name}")
# Concatenate contents, in order per +debsigs+ documentation.
shellout!("cat debian-binary control.tar.* data.tar.* > complete")
# Create signature (as +root+)
gpg_command = "#{gpg} --armor --sign --detach-sign"
gpg_command << " --local-user '#{project.maintainer}'"
gpg_command << " --homedir #{ENV['HOME']}/.gnupg" # TODO: Make this configurable
## pass the +signing_passphrase+ via +STDIN+
gpg_command << " --batch --no-tty"
## Check `gpg` for the compatibility/need of pinentry-mode
# - We're calling gpg with the +--pinentry-mode+ argument, and +STDIN+ of +/dev/null+
# - This _will_ fail with exit code 2 no matter what. We want to check the +STDERR+
# for the error message about the parameter. If it is _not present_ in the
# output, then we _do_ want to add it. (If +grep -q+ is +1+, add parameter)
if shellout("#{gpg} --pinentry-mode loopback </dev/null 2>&1 | grep -q pinentry-mode").exitstatus == 1
gpg_command << " --pinentry-mode loopback"
end
gpg_command << " --passphrase-fd 0"
gpg_command << " -o _gpgorigin complete"
shellout!("fakeroot #{gpg_command}", input: signing_passphrase)
# Append +_gpgorigin+ to the +.deb+ file (as +root+)
shellout!("fakeroot ar rc #{Config.package_dir}/#{package_name} _gpgorigin")
end
end
else
log.info(log_key) { "Signing not possible. Ensure that GnuPG and GNU AR are available" }
end
end
#
# The size of this Debian package. This is dynamically calculated.
#
......
......@@ -337,6 +337,46 @@ module Omnibus
end
end
describe "#sign_deb_file", :not_supported_on_windows do
context "when DEB signing is not enabled" do
before do
subject.signing_passphrase(nil)
end
it "logs a message" do
output = capture_logging { subject.sign_deb_file }
expect(output).to include("Signing not enabled for .deb file")
end
end
context "when DEB signing is enabled" do
before do
allow(subject).to receive(:shellout!)
allow(subject).to receive(:package_name).and_return("safe")
subject.signing_passphrase("foobar")
end
it "logs a message" do
output = capture_logging { subject.sign_deb_file }
expect(output).to include("Signing enabled for .deb file")
end
it "finds gpg and ar commands" do
output = capture_logging { subject.sign_deb_file }
expect(output).not_to include("Signing not possible.")
end
it "runs correct commands" do
expect(subject).to receive(:shellout!)
.at_least(:once).with(/ar x/)
.at_least(:once).with(/cat debian-binary control\.tar/)
.at_least(:once).with(/fakeroot gpg/)
.at_least(:once).with(/fakeroot ar rc/)
subject.sign_deb_file
end
end
end
describe "#package_size" do
before do
project.install_dir(staging_dir)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment