Skip to content

Commit

Permalink
cli: --offline means fully offline (#1143)
Browse files Browse the repository at this point in the history
  • Loading branch information
woodruffw authored Sep 26, 2024
1 parent 32919e1 commit af8c575
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ All versions prior to 0.9.0 are untracked.

## [Unreleased]

### Changed

* CLI: When verifying, the `--offline` flag now fully disables all online
operations, including routine local TUF repository refreshes
([#1143](https://github.com/sigstore/sigstore-python/pull/1143))

## [3.3.0]

### Added
Expand Down
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ else!
* [Signing with ambient credentials](#signing-with-ambient-credentials)
* [Signing with an email identity](#signing-with-an-email-identity)
* [Signing with an explicit identity token](#signing-with-an-explicit-identity-token)
* [Verifying against a signature and certificate](#verifying-against-a-signature-and-certificate)
* [Verifying against a bundle](#verifying-against-a-bundle)
* [Offline verification](#offline-verification)
* [Verifying a digest instead of a file](#verifying-a-digest-instead-of-a-file)
* [Verifying signatures from GitHub Actions](#verifying-signatures-from-github-actions)
* [Licensing](#licensing)
* [Community](#community)
Expand Down Expand Up @@ -402,7 +404,7 @@ $ python -m sigstore sign --identity-token YOUR-LONG-JWT-HERE foo.txt
Note that passing a custom identity token does not circumvent Fulcio's requirements,
namely the Fulcio's supported identity providers and the claims expected within the token.

### Verifying against a signature and certificate
### Verifying against a bundle

By default, `sigstore verify identity` will attempt to find a `<filename>.sigstore.json`
or `<filename>.sigstore` in the same directory as the file being verified:
Expand All @@ -423,6 +425,50 @@ $ python -m sigstore verify identity foo.txt bar.txt \
--cert-oidc-issuer 'https://github.com/login/oauth'
```

### Offline verification

> [!IMPORTANT]
> Because `--offline` disables trust root updates, `sigstore-python` falls back
> to the latest cached trust root or, if none exists, the trust root baked
> into `sigstore-python` itself. Like with any other offline verification,
> this means that users may miss trust root changes (such as new root keys,
> or revocations) unless they separately keep the trust root up-to-date.
>
> Users who need to operationalize offline verification may wish to do this
> by distributing their own trust configuration; see
> [Configuring a custom root of trust](#configuring-a-custom-root-of-trust-byo-pki).
During verification, there are two kinds of network access that `sigstore-python`
*can* perform:

1. When verifying against "detached" materials (e.g. separate `.crt` and `.sig`
files), `sigstore-python` can perform an online transparency log lookup.
2. By default, during all verifications, `sigstore-python` will attempt to
refresh the locally cached root of trust via a TUF update.

When performing bundle verification (i.e. `.sigstore` or `.sigstore.json`),
(1) does not apply. However, (2) can still result in online accesses.

To perform **fully** offline verification, pass `--offline` to your
`sigstore verify` subcommand:

```bash
$ python -m sigstore verify identity foo.txt \
--offline \
--cert-identity '[email protected]' \
--cert-oidc-issuer 'https://github.com/login/oauth'
```

Alternatively, users may choose to bypass TUF entirely by passing
an entire trust configuration to `sigstore-python` via `--trust-config`:

```bash
$ python -m sigstore --trust-config public.trustconfig.json verify identity ...
```

This will similarly result in fully offline operation, as the trust
configuration contains a full trust root.

### Verifying a digest instead of a file

`sigstore-python` supports verifying digests directly, without requiring the artifact to be
Expand Down
4 changes: 2 additions & 2 deletions sigstore/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,12 +996,12 @@ def _collect_verification_state(

if args.staging:
_logger.debug("verify: staging instances requested")
verifier = Verifier.staging()
verifier = Verifier.staging(offline=args.offline)
elif args.trust_config:
trust_config = ClientTrustConfig.from_json(args.trust_config.read_text())
verifier = Verifier._from_trust_config(trust_config)
else:
verifier = Verifier.production()
verifier = Verifier.production(offline=args.offline)

all_materials = []
for file_or_hashed, materials in input_map.items():
Expand Down
6 changes: 5 additions & 1 deletion sigstore/_internal/tuf.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ def __init__(self, url: str, offline: bool = False) -> None:
_logger.debug(f"TUF targets cache: {self._targets_dir}")

self._updater: None | Updater = None
if not offline:
if offline:
_logger.warning(
"TUF repository is loaded in offline mode; updates will not be performed"
)
else:
# Initialize and update the toplevel TUF metadata
self._updater = Updater(
metadata_dir=str(self._metadata_dir),
Expand Down
8 changes: 4 additions & 4 deletions sigstore/verify/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,23 @@ def __init__(self, *, rekor: RekorClient, trusted_root: TrustedRoot):
self._trusted_root = trusted_root

@classmethod
def production(cls) -> Verifier:
def production(cls, *, offline: bool = False) -> Verifier:
"""
Return a `Verifier` instance configured against Sigstore's production-level services.
"""
return cls(
rekor=RekorClient.production(),
trusted_root=TrustedRoot.production(),
trusted_root=TrustedRoot.production(offline=offline),
)

@classmethod
def staging(cls) -> Verifier:
def staging(cls, *, offline: bool = False) -> Verifier:
"""
Return a `Verifier` instance configured against Sigstore's staging-level services.
"""
return cls(
rekor=RekorClient.staging(),
trusted_root=TrustedRoot.staging(),
trusted_root=TrustedRoot.staging(offline=offline),
)

@classmethod
Expand Down
13 changes: 12 additions & 1 deletion test/unit/verify/test_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,27 @@ def test_verifier_multiple_verifications(signing_materials, null_policy):
verifier.verify_artifact(file.read_bytes(), bundle, null_policy)


@pytest.mark.online
@pytest.mark.parametrize(
"filename", ("bundle.txt", "bundle_v3.txt", "bundle_v3_alt.txt")
)
def test_verifier_bundle(signing_bundle, null_policy, mock_staging_tuf, filename):
def test_verifier_bundle(signing_bundle, null_policy, filename):
(file, bundle) = signing_bundle(filename)

verifier = Verifier.staging()
verifier.verify_artifact(file.read_bytes(), bundle, null_policy)


@pytest.mark.parametrize(
"filename", ("bundle.txt", "bundle_v3.txt", "bundle_v3_alt.txt")
)
def test_verifier_bundle_offline(signing_bundle, null_policy, filename):
(file, bundle) = signing_bundle(filename)

verifier = Verifier.staging(offline=True)
verifier.verify_artifact(file.read_bytes(), bundle, null_policy)


@pytest.mark.staging
def test_verifier_email_identity(signing_materials):
verifier = Verifier.staging()
Expand Down

0 comments on commit af8c575

Please sign in to comment.