-
Notifications
You must be signed in to change notification settings - Fork 56
_cli: add sigstore verify identity
#379
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
Changes from all commits
14a9048
2eaded6
7ed3aa1
76f54a2
7e43fbc
9f95092
f70a881
9d92fbe
769bafd
3ab05a7
b62e39c
088714b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,15 +78,16 @@ a tool for signing and verifying Python package distributions | |
positional arguments: | ||
{sign,verify,get-identity-token} | ||
|
||
options: | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
-V, --version show program's version number and exit | ||
-v, --verbose run with additional debug logging; supply multiple | ||
times to increase verbosity (default: 0) | ||
``` | ||
<!-- @end-sigstore-help@ --> | ||
|
||
Signing: | ||
|
||
### Signing | ||
|
||
<!-- @begin-sigstore-sign-help@ --> | ||
``` | ||
|
@@ -102,7 +103,7 @@ usage: sigstore sign [-h] [--identity-token TOKEN] [--oidc-client-id ID] | |
positional arguments: | ||
FILE The file to sign | ||
|
||
options: | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
|
||
OpenID Connect options: | ||
|
@@ -150,22 +151,30 @@ Sigstore instance options: | |
``` | ||
<!-- @end-sigstore-sign-help@ --> | ||
|
||
Verifying: | ||
### Verifying | ||
|
||
#### Identities | ||
|
||
<!-- @begin-sigstore-verify-help@ --> | ||
This is the most common verification done with `sigstore`, and therefore | ||
the one you probably want: you can use it to verify that a signature was | ||
produced by a particular identity (like `[email protected]`), as attested | ||
to by a particular OIDC provider (like `https://github.com/login/oauth`). | ||
|
||
<!-- @begin-sigstore-verify-identity-help@ --> | ||
``` | ||
usage: sigstore verify [-h] [--certificate FILE] [--signature FILE] | ||
[--rekor-bundle FILE] [--certificate-chain FILE] | ||
[--cert-email EMAIL] --cert-identity IDENTITY | ||
--cert-oidc-issuer URL [--require-rekor-offline] | ||
[--staging] [--rekor-url URL] | ||
[--rekor-root-pubkey FILE] | ||
FILE [FILE ...] | ||
usage: sigstore verify identity [-h] [--certificate FILE] [--signature FILE] | ||
[--rekor-bundle FILE] | ||
[--certificate-chain FILE] | ||
[--cert-email EMAIL] --cert-identity IDENTITY | ||
--cert-oidc-issuer URL | ||
[--require-rekor-offline] [--staging] | ||
[--rekor-url URL] [--rekor-root-pubkey FILE] | ||
FILE [FILE ...] | ||
|
||
positional arguments: | ||
FILE The file to verify | ||
|
||
options: | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
|
||
Verification inputs: | ||
|
@@ -203,7 +212,11 @@ Sigstore instance options: | |
A PEM-encoded root public key for Rekor itself | ||
(conflicts with --staging) (default: None) | ||
``` | ||
<!-- @end-sigstore-verify-help@ --> | ||
<!-- @end-sigstore-verify-identity-help@ --> | ||
|
||
For backwards compatibility, `sigstore verify [args ...]` is equivalent to | ||
`sigstore verify identity [args ...]`, but the latter form is **strongly** | ||
preferred. | ||
|
||
## Example uses | ||
|
||
|
@@ -270,51 +283,31 @@ same directory as the file being verified: | |
|
||
```console | ||
# looks for foo.txt.sig and foo.txt.crt | ||
$ python -m sigstore verify foo.txt | ||
$ python -m sigstore verify identity foo.txt \ | ||
--cert-identity '[email protected]' \ | ||
--cert-oidc-issuer 'https://github.com/login/oauth' | ||
``` | ||
|
||
Multiple files can be verified at once: | ||
|
||
```console | ||
# looks for {foo,bar}.txt.{sig,crt} | ||
$ python -m sigstore verify foo.txt bar.txt | ||
$ python -m sigstore verify identity foo.txt bar.txt \ | ||
--cert-identity '[email protected]' \ | ||
--cert-oidc-issuer 'https://github.com/login/oauth' | ||
``` | ||
|
||
If your signature and certificate are at different paths, you can specify them | ||
explicitly (but only for one file at a time): | ||
|
||
```console | ||
$ python -m sigstore verify \ | ||
$ python -m sigstore verify identity foo.txt \ | ||
--certificate some/other/path/foo.crt \ | ||
--signature some/other/path/foo.sig \ | ||
foo.txt | ||
``` | ||
|
||
### Extended verification against OpenID Connect claims | ||
|
||
By default, `sigstore verify` only checks the validity of the certificate, | ||
the correctness of the signature, and the consistency of both with the | ||
certificate transparency log. | ||
|
||
To assert further details about the signature (such as *who* or *what* signed for the artifact), | ||
you can test against the OpenID Connect claims embedded within it. | ||
|
||
For example, to accept the signature and certificate only if they correspond to a particular | ||
email identity: | ||
|
||
```console | ||
$ python -m sigstore verify --cert-email [email protected] foo.txt | ||
``` | ||
|
||
Or to accept only if the OpenID Connect issuer is the expected one: | ||
|
||
```console | ||
$ python -m sigstore verify --cert-oidc-issuer https://github.com/login/oauth foo.txt | ||
--cert-identity '[email protected]' \ | ||
--cert-oidc-issuer 'https://github.com/login/oauth' | ||
``` | ||
|
||
These options can be combined, and further extended validation options (e.g., for | ||
signing results from GitHub Actions) are under development. | ||
|
||
## Licensing | ||
|
||
`sigstore` is licensed under the Apache 2.0 License. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,6 +82,42 @@ def _boolify_env(envvar: str) -> bool: | |
raise ValueError(f"can't coerce '{val}' to a boolean") | ||
|
||
|
||
def _set_default_verify_subparser(parser: argparse.ArgumentParser, name: str) -> None: | ||
""" | ||
An argparse patch for configuring a default subparser for `sigstore verify`. | ||
|
||
Adapted from <https://stackoverflow.com/a/26379693> | ||
""" | ||
subparser_found = False | ||
for arg in sys.argv[1:]: | ||
if arg in ["-h", "--help"]: # global help if no subparser | ||
break | ||
else: | ||
for x in parser._subparsers._actions: # type: ignore[union-attr] | ||
if not isinstance(x, argparse._SubParsersAction): | ||
continue | ||
for sp_name in x._name_parser_map.keys(): | ||
if sp_name in sys.argv[1:]: | ||
subparser_found = True | ||
if not subparser_found: | ||
try: | ||
# If `sigstore verify identity` wasn't passed explicitly, we need | ||
# to insert the `identity` subcommand into the correct position | ||
# within `sys.argv`. To do that, we get the index of the `verify` | ||
# subcommand, and insert it directly after it. | ||
verify_idx = sys.argv.index("verify") | ||
sys.argv.insert(verify_idx + 1, name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we maybe log a warning here? I realise we don't want to make a breaking change but perhaps we can at least mark it as deprecated and slate it for future removal. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, a log sounds good. I'll do that in a moment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
logger.warning( | ||
"`sigstore verify` without a subcommand will be treated as " | ||
"`sigstore verify identity`, but this behavior will be deprecated " | ||
"in a future release" | ||
) | ||
except ValueError: | ||
# This happens when we invoke `sigstore sign`, since there's no | ||
# `verify` subcommand to insert under. We do nothing in this case. | ||
pass | ||
|
||
|
||
def _add_shared_instance_options(group: argparse._ArgumentGroup) -> None: | ||
group.add_argument( | ||
"--staging", | ||
|
@@ -243,10 +279,18 @@ def _parser() -> argparse.ArgumentParser: | |
|
||
# `sigstore verify` | ||
verify = subcommands.add_parser( | ||
"verify", formatter_class=argparse.ArgumentDefaultsHelpFormatter | ||
"verify", | ||
formatter_class=argparse.ArgumentDefaultsHelpFormatter, | ||
) | ||
verify_subcommand = verify.add_subparsers(dest="verify_subcommand") | ||
|
||
input_options = verify.add_argument_group("Verification inputs") | ||
# `sigstore verify identity` | ||
verify_identity = verify_subcommand.add_parser( | ||
"identity", | ||
help="verify against a known identity and identity provider", | ||
formatter_class=argparse.ArgumentDefaultsHelpFormatter, | ||
) | ||
input_options = verify_identity.add_argument_group("Verification inputs") | ||
input_options.add_argument( | ||
"--certificate", | ||
"--cert", | ||
|
@@ -270,7 +314,9 @@ def _parser() -> argparse.ArgumentParser: | |
help="The offline Rekor bundle to verify with; not used with multiple inputs", | ||
) | ||
|
||
verification_options = verify.add_argument_group("Extended verification options") | ||
verification_options = verify_identity.add_argument_group( | ||
"Extended verification options" | ||
) | ||
verification_options.add_argument( | ||
"--certificate-chain", | ||
metavar="FILE", | ||
|
@@ -309,17 +355,21 @@ def _parser() -> argparse.ArgumentParser: | |
help="Require offline Rekor verification with a bundle; implied by --rekor-bundle", | ||
) | ||
|
||
instance_options = verify.add_argument_group("Sigstore instance options") | ||
instance_options = verify_identity.add_argument_group("Sigstore instance options") | ||
_add_shared_instance_options(instance_options) | ||
|
||
verify.add_argument( | ||
verify_identity.add_argument( | ||
"files", | ||
metavar="FILE", | ||
type=Path, | ||
nargs="+", | ||
help="The file to verify", | ||
) | ||
|
||
# `sigstore verify` defaults to `sigstore verify identity`, for backwards | ||
# compatibility. | ||
_set_default_verify_subparser(verify, "identity") | ||
|
||
# `sigstore get-identity-token` | ||
get_identity_token = subcommands.add_parser("get-identity-token") | ||
_add_shared_oidc_options(get_identity_token) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoa... how did I not know that this exists.