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

cli: --offline means fully offline #1143

Merged
merged 5 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 'hamilcar@example.com' \
--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
Loading