Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signature verification support #15

Open
wchargin opened this issue Dec 20, 2018 · 4 comments · May be fixed by #17 or #192
Open

Signature verification support #15

wchargin opened this issue Dec 20, 2018 · 4 comments · May be fixed by #17 or #192
Labels
P2 We'll consider working on this in future. (Assignee optional) type: feature request New feature or request

Comments

@wchargin
Copy link

Bazel release candidates and releases are accompanied by PGP signatures:

$ wget -q 'https://releases.bazel.build/0.20.0/rc1/bazel-0.20.0rc1-darwin-x86_64'
$ wget -q 'https://releases.bazel.build/0.20.0/rc1/bazel-0.20.0rc1-darwin-x86_64.sig'
$ gpg --verify ./bazel-0.20.0rc1-darwin-x86_64.sig
gpg: assuming signed data in './bazel-0.20.0rc1-darwin-x86_64'
gpg: Signature made Mon 05 Nov 2018 06:16:24 AM PST
gpg:                using RSA key 71A1D0EFCFEB6281FD0437C93D5919B448457EE0
gpg: Good signature from "Bazel Developer (Bazel APT repository key) <[email protected]>" [full]
$ wget -q 'https://releases.bazel.build/0.21.0/release/bazel-0.21.0-linux-x86_64'
$ wget -q 'https://releases.bazel.build/0.21.0/release/bazel-0.21.0-linux-x86_64.sig'
$ gpg --verify ./bazel-0.21.0-linux-x86_64.sig
gpg: assuming signed data in './bazel-0.21.0-linux-x86_64'
gpg: Signature made Wed 19 Dec 2018 05:58:23 AM PST
gpg:                using RSA key 71A1D0EFCFEB6281FD0437C93D5919B448457EE0
gpg: Good signature from "Bazel Developer (Bazel APT repository key) <[email protected]>" [full]

Would you be interested in a pull request to make Bazelisk additionally
download and verify these signatures? (Signature verification is fast:
about a second on my machine.)

The Bazel team public key would be hard-coded into the repository, and
we can use

$ gpg --no-default-keyring --keyring ./bazel-release.pub.gpg \
> --trust-model always --verify "${SIGNATURE}" "${BINARY}"

to verify the binary. (This requires the bazel-release.pub.gpg key to
be dearmored, but that’s easy to do.)

@philwo
Copy link
Member

philwo commented Dec 21, 2018

Would you be interested in a pull request to make Bazelisk additionally download and verify these signatures?

Absolutely! That sounds like a very useful feature. I'd love to review a PR for this. :)

@wchargin
Copy link
Author

Okay, thanks. I’ll take a look and send a PR when I get the chance.

Let me make sure that I understand this project’s constraints: is the
main module supposed to run as a standalone Python script, with no file
dependencies or Python package dependencies (other than those installed
by default), in Python 2 and 3, and in Windows, Mac, and Linux?

@philwo
Copy link
Member

philwo commented Dec 21, 2018

Great, thanks!

Yes, the current version has these properties:

  • Works with Python 2.7 and Python 3.x.
  • Works on Linux, macOS and Windows.
  • Doesn't require any packages except what's in the Python standard library.

However:

  • I would be OK with dropping Python 2.7 support if it makes things easier or if we can avoid adding a third-party package dependency then.
  • Depending on packages (maybe optionally?) might be OK, if it's a reasonable dependency and we can't use something from the standard library. But it's nice that it currently just works without any required deps, so if possible, we should keep it this way.
  • Regarding the platforms, we have to support all three. I'm happy to test your PR on platforms you don't easily have access to, if that helps. (I'll also add automatic testing of pull requests on Buildkite soon, so that'll be easier.)

wchargin added a commit to wchargin/bazelisk that referenced this issue Dec 22, 2018
Summary:
This commit adds logic to authenticate all Bazel binaries that are
downloaded, as long as the user has GPG installed. If a user does not
have GPG installed, a new warning will be printed when a binary is
downloaded, but Bazelisk will function the same way as before. (GPG is
installed by default on Debian and Ubuntu.)

No new subprocesses are spawned when an already-downloaded version of
Bazel is run. The only appreciable overhead is incurred at download
time.

Resolves bazelbuild#15.

Test Plan:

  - Remove the `~/.bazelisk` directory. Run `./bazelisk.py version`.
    Note that it downloads the latest binary and the latest signature,
    then prints “Authenticity verified” before invoking Bazel.

  - Run `./bazelisk.py version` again. Note that it does not verify the
    signature.

  - Remove the `~/.bazelisk` directory. Symlink `/bin/false` to
    `~/bin/gpg`, and ensure that the symlink precedes the real `gpg` on
    your path. Run Bazelisk, and note that it prints a warning that GPG
    is not available but executes Bazel anyway. Run Bazelisk again, and
    note that it does not print the warning (because it reuses the
    existing executable without reauthenticating). Remove the symlink.

  - Remove the `~/.bazelisk` directory. Edit `bazelisk.py`, changing the
    `determine_urls` function so that the returned `binary_url` is an
    arbitrary web page (like `http://example.com/`) but the signature
    URL is unchanged. Run Bazelisk, and note that Bazelisk reports,
    “Failed to authenticate binary!”, includes the GPG output (“BAD
    signature”), and aborts with exit code 2 _without_ invoking Bazel.
    Run `ls ~/.bazelisk/bin` and note that it does not include the
    invalid binary (though the signature is still there). Revert the
    changes to `bazelisk.py`.

  - Remove the `~/.bazelisk` directory. Create an arbitrary document and
    use `gpg --detach-sign` to sign it with a key that is not the Bazel
    signing key. Spawn a web server (`python -m SimpleHTTPServer`) to
    serve the “malicious executable” and its signature. Edit
    `bazelisk.py`, changing the `determine_urls` function to point both
    the binary and the signature to this local web server. Run Bazelisk,
    and note that it fails to authenticate the binary, with the message
    “public key not found”.

Repeat the above steps in Python 2 and Python 3.

Verify that your personal GnuPG database has not been modified (in
particular, the Bazel key should not have been installed, and the trust
settings should not have been modified).

I have tested this on Linux with gpg (GnuPG) 1.4.20. I don’t see any
reason that it shouldn’t work on macOS or Windows as long as the gpg(1)
interfaces are the same.

wchargin-branch: authenticate-binaries
@wchargin wchargin linked a pull request Dec 22, 2018 that will close this issue
@wchargin
Copy link
Author

Submitted #17, preserving all those properties (no new dependencies).

Regarding the platforms, we have to support all three. I'm happy to
test your PR on platforms you don't easily have access to, if that
helps. (I'll also add automatic testing of pull requests on Buildkite
soon, so that'll be easier.)

Yes: if you could please test this on macOS, that’d be great. My
understanding is that macOS does not ship with gpg(1), so it is
important that the fallback behavior be correct, and of course the main
functionality should work for those who have installed GPG.

I see that Windows testing is an “idea for the future” according to the
README, so I’ll let you decide how much testing should be done on that
side of the pond. :-)

wchargin added a commit to wchargin/bazelisk that referenced this issue Jan 18, 2019
Summary:
This commit adds logic to authenticate all Bazel binaries that are
downloaded, as long as the user has GPG installed. If a user does not
have GPG installed, a new warning will be printed when a binary is
downloaded, but Bazelisk will function the same way as before. (GPG is
installed by default on Debian and Ubuntu.)

No new subprocesses are spawned when an already-downloaded version of
Bazel is run. The only appreciable overhead is incurred at download
time.

Resolves bazelbuild#15.

Test Plan:

  - Remove the `~/.bazelisk` directory. Run `./bazelisk.py version`.
    Note that it downloads the latest binary and the latest signature,
    then prints “Authenticity verified” before invoking Bazel.

  - Run `./bazelisk.py version` again. Note that it does not verify the
    signature.

  - Remove the `~/.bazelisk` directory. Symlink `/bin/false` to
    `~/bin/gpg`, and ensure that the symlink precedes the real `gpg` on
    your path. Run Bazelisk, and note that it prints a warning that GPG
    is not available but executes Bazel anyway. Run Bazelisk again, and
    note that it does not print the warning (because it reuses the
    existing executable without reauthenticating). Remove the symlink.

  - Remove the `~/.bazelisk` directory. Edit `bazelisk.py`, changing the
    `determine_urls` function so that the returned `binary_url` is an
    arbitrary web page (like `http://example.com/`) but the signature
    URL is unchanged. Run Bazelisk, and note that Bazelisk reports,
    “Failed to authenticate binary!”, includes the GPG output (“BAD
    signature”), and aborts with exit code 2 _without_ invoking Bazel.
    Run `ls ~/.bazelisk/bin` and note that it does not include the
    invalid binary (though the signature is still there). Revert the
    changes to `bazelisk.py`.

  - Remove the `~/.bazelisk` directory. Create an arbitrary document and
    use `gpg --detach-sign` to sign it with a key that is not the Bazel
    signing key. Spawn a web server (`python -m SimpleHTTPServer`) to
    serve the “malicious executable” and its signature. Edit
    `bazelisk.py`, changing the `determine_urls` function to point both
    the binary and the signature to this local web server. Run Bazelisk,
    and note that it fails to authenticate the binary, with the message
    “public key not found”.

Repeat the above steps in Python 2 and Python 3.

Verify that your personal GnuPG database has not been modified (in
particular, the Bazel key should not have been installed, and the trust
settings should not have been modified).

I have tested this on Linux with gpg (GnuPG) 1.4.20. I don’t see any
reason that it shouldn’t work on macOS or Windows as long as the gpg(1)
interfaces are the same.

wchargin-branch: authenticate-binaries
wchargin added a commit to wchargin/bazelisk that referenced this issue Jan 20, 2019
Summary:
This commit adds logic to authenticate all Bazel binaries that are
downloaded, as long as the user has GPG installed. If a user does not
have GPG installed, a new warning will be printed when a binary is
downloaded, but Bazelisk will function the same way as before. (GPG is
installed by default on Debian and Ubuntu.)

No new subprocesses are spawned when an already-downloaded version of
Bazel is run. The only appreciable overhead is incurred at download
time.

Resolves bazelbuild#15.

Test Plan:

  - Remove the `~/.bazelisk` directory. Run `./bazelisk.py version`.
    Note that it downloads the latest binary and the latest signature,
    then prints “Authenticity verified” before invoking Bazel.

  - Run `./bazelisk.py version` again. Note that it does not verify the
    signature.

  - Remove the `~/.bazelisk` directory. Symlink `/bin/false` to
    `~/bin/gpg`, and ensure that the symlink precedes the real `gpg` on
    your path. Run Bazelisk, and note that it prints a warning that GPG
    is not available but executes Bazel anyway. Run Bazelisk again, and
    note that it does not print the warning (because it reuses the
    existing executable without reauthenticating). Remove the symlink.

  - Remove the `~/.bazelisk` directory. Edit `bazelisk.py`, changing the
    `determine_urls` function so that the returned `binary_url` is an
    arbitrary web page (like `http://example.com/`) but the signature
    URL is unchanged. Run Bazelisk, and note that Bazelisk reports,
    “Failed to authenticate binary!”, includes the GPG output (“BAD
    signature”), and aborts with exit code 2 _without_ invoking Bazel.
    Run `ls ~/.bazelisk/bin` and note that it does not include the
    invalid binary (though the signature is still there). Revert the
    changes to `bazelisk.py`.

  - Remove the `~/.bazelisk` directory. Create an arbitrary document and
    use `gpg --detach-sign` to sign it with a key that is not the Bazel
    signing key. Spawn a web server (`python -m SimpleHTTPServer`) to
    serve the “malicious executable” and its signature. Edit
    `bazelisk.py`, changing the `determine_urls` function to point both
    the binary and the signature to this local web server. Run Bazelisk,
    and note that it fails to authenticate the binary, with the message
    “public key not found”.

Repeat the above steps in Python 2 and Python 3.

Verify that your personal GnuPG database has not been modified (in
particular, the Bazel key should not have been installed, and the trust
settings should not have been modified).

I have tested this on Linux with gpg (GnuPG) 1.4.20. I don’t see any
reason that it shouldn’t work on macOS or Windows as long as the gpg(1)
interfaces are the same.

wchargin-branch: authenticate-binaries
@philwo philwo added the type: feature request New feature or request label Jun 19, 2019
@philwo philwo self-assigned this Jun 19, 2019
PiotrSikora added a commit to PiotrSikora/bazelisk that referenced this issue Oct 29, 2020
PiotrSikora added a commit to PiotrSikora/bazelisk that referenced this issue Oct 29, 2020
PiotrSikora added a commit to PiotrSikora/bazelisk that referenced this issue Oct 29, 2020
PiotrSikora added a commit to PiotrSikora/bazelisk that referenced this issue Oct 29, 2020
@philwo philwo removed their assignment Sep 14, 2023
@fweikert fweikert added the P2 We'll consider working on this in future. (Assignee optional) label Feb 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P2 We'll consider working on this in future. (Assignee optional) type: feature request New feature or request
Projects
None yet
3 participants