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

feat: Add --oauth-force-oob CLI option #667

Merged
merged 15 commits into from
Jun 9, 2023
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ All versions prior to 0.9.0 are untracked.

## [Unreleased]

### Added

* CLI: `sigstore sign` and `sigstore get-identity-token` now support the
`--oauth-force-oob` option; which has the same behavior as the
preexisting `SIGSTORE_OAUTH_FORCE_OOB` environment variable
([#667](https://github.com/sigstore/sigstore-python/pull/667))

### Changed

* `sigstore verify` now performs additional verification of Rekor's inclusion
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ Sigstore instance options:
usage: sigstore sign [-h] [--identity-token TOKEN] [--oidc-client-id ID]
[--oidc-client-secret SECRET]
[--oidc-disable-ambient-providers] [--oidc-issuer URL]
[--no-default-files] [--signature FILE]
[--certificate FILE] [--bundle FILE]
[--oauth-force-oob] [--no-default-files]
[--signature FILE] [--certificate FILE] [--bundle FILE]
[--output-directory DIR] [--overwrite] [--staging]
[--rekor-url URL] [--rekor-root-pubkey FILE]
[--fulcio-url URL] [--ctfe FILE]
Expand All @@ -160,6 +160,9 @@ OpenID Connect options:
(e.g. on GitHub Actions) (default: False)
--oidc-issuer URL The OpenID Connect issuer to use (conflicts with
--staging) (default: https://oauth2.sigstore.dev/auth)
--oauth-force-oob Force an out-of-band OAuth flow and do not
automatically start the default web browser (default:
False)

Output options:
--no-default-files Don't emit the default output files ({input}.sigstore)
Expand Down
10 changes: 9 additions & 1 deletion sigstore/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ def _add_shared_oidc_options(
default=os.getenv("SIGSTORE_OIDC_ISSUER", DEFAULT_OAUTH_ISSUER_URL),
help="The OpenID Connect issuer to use (conflicts with --staging)",
)
group.add_argument(
"--oauth-force-oob",
action="store_true",
default=_boolify_env("SIGSTORE_OAUTH_FORCE_OOB"),
help="Force an out-of-band OAuth flow and do not automatically start the default web browser",
)


def _parser() -> argparse.ArgumentParser:
Expand Down Expand Up @@ -977,7 +983,9 @@ def _get_identity(args: argparse.Namespace) -> Optional[IdentityToken]:
args.oidc_client_secret = "" # nosec: B105

token = issuer.identity_token(
client_id=args.oidc_client_id, client_secret=args.oidc_client_secret
client_id=args.oidc_client_id,
client_secret=args.oidc_client_secret,
force_oob=args.oauth_force_oob,
)

return token
19 changes: 12 additions & 7 deletions sigstore/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from __future__ import annotations

import logging
import os
import sys
import time
import urllib.parse
Expand Down Expand Up @@ -293,22 +292,26 @@ def staging(cls) -> Issuer:
return cls(STAGING_OAUTH_ISSUER_URL)

def identity_token( # nosec: B107
self, client_id: str = "sigstore", client_secret: str = ""
self,
client_id: str = "sigstore",
client_secret: str = "",
force_oob: bool = False,
) -> IdentityToken:
"""
Retrieves and returns an `IdentityToken` from the current `Issuer`, via OAuth.
woodruffw marked this conversation as resolved.
Show resolved Hide resolved

This function blocks on user interaction, either via a web browser or an out-of-band
OAuth flow.
This function blocks on user interaction.

The `force_oob` flag controls the kind of flow performed. When `False` (the default),
this function attempts to open the user's web browser before falling back to
an out-of-band flow. When `True`, the out-of-band flow is always used.
"""

# This function and the components that it relies on are based off of:
# https://github.com/psteniusubi/python-sample

from sigstore._internal.oidc.oauth import _OAuthFlow

force_oob = os.getenv("SIGSTORE_OAUTH_FORCE_OOB") is not None

code: str
with _OAuthFlow(client_id, client_secret, self) as server:
# Launch web browser
Expand Down Expand Up @@ -361,7 +364,9 @@ def identity_token( # nosec: B107
try:
resp.raise_for_status()
except requests.HTTPError as http_error:
raise IdentityError from http_error
raise IdentityError(
f"Token request failed with {resp.status_code}"
) from http_error

token_json = resp.json()
token_error = token_json.get("error")
Expand Down
7 changes: 3 additions & 4 deletions test/unit/internal/oidc/test_issuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ def test_init_url():


@pytest.mark.online
def test_get_identity_token_identity_error(monkeypatch):
monkeypatch.setenv("SIGSTORE_OAUTH_FORCE_OOB", "")
def test_get_identity_token_bad_code(monkeypatch):
monkeypatch.setattr("builtins.input", lambda _: "hunter2")

with pytest.raises(IdentityError):
Issuer.staging().identity_token()
with pytest.raises(IdentityError, match=r"^Token request failed with .+$"):
Issuer.staging().identity_token(force_oob=True)