Skip to content

feat:(oidc) derive audience claim from client_id in IdentityToken #1402

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ All versions prior to 0.9.0 are untracked.

* Added support for ed25519 keys.
[#1377](https://github.com/sigstore/sigstore-python/pull/1377)
* Added client_id as the audience (aud) claim when initializing IdentityToken
[#1402](https://github.com/sigstore/sigstore-python/pull/1402)


### Fixed

Expand Down
53 changes: 26 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,22 @@ usage: sigstore [-h] [-v] [-V] [--staging | --trust-config FILE] COMMAND ...
a tool for signing and verifying Python package distributions

positional arguments:
COMMAND the operation to perform
attest sign one or more inputs using DSSE
sign sign one or more inputs
verify verify one or more inputs
get-identity-token
retrieve and return a Sigstore-compatible OpenID
Connect token
plumbing developer-only plumbing operations

optional arguments:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
-V, --version show program's version number and exit
--staging Use sigstore's staging instances, instead of the
default production instances (default: False)
--trust-config FILE The client trust configuration to use (default: None)
COMMAND the operation to perform
attest sign one or more inputs using DSSE
sign sign one or more inputs
verify verify one or more inputs
get-identity-token retrieve and return a Sigstore-compatible OpenID
Connect token
plumbing developer-only plumbing operations

options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
-V, --version show program's version number and exit
--staging Use sigstore's staging instances, instead of the
default production instances (default: False)
--trust-config FILE The client trust configuration to use (default: None)
```
<!-- @end-sigstore-help@ -->

Expand All @@ -105,7 +104,7 @@ usage: sigstore sign [-h] [-v] [--identity-token TOKEN] [--oidc-client-id ID]
positional arguments:
FILE The file to sign

optional arguments:
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
Expand All @@ -129,10 +128,10 @@ OpenID Connect options:
Output options:
--no-default-files Don't emit the default output files
({input}.sigstore.json) (default: False)
--signature FILE, --output-signature FILE
--signature, --output-signature FILE
Write a single signature to the given file; does not
work with multiple input files (default: None)
--certificate FILE, --output-certificate FILE
--certificate, --output-certificate FILE
Write a single certificate to the given file; does not
work with multiple input files (default: None)
--bundle FILE Write a single Sigstore bundle to the given file; does
Expand Down Expand Up @@ -161,7 +160,7 @@ usage: sigstore attest [-h] [-v] --predicate FILE --predicate-type TYPE
positional arguments:
FILE The file to sign

optional arguments:
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
Expand Down Expand Up @@ -205,17 +204,17 @@ Output options:
```
usage: sigstore verify identity [-h] [-v] [--certificate FILE]
[--signature FILE] [--bundle FILE] [--offline]
--cert-identity IDENTITY --cert-oidc-issuer
URL
--cert-identity IDENTITY
--cert-oidc-issuer URL
FILE_OR_DIGEST [FILE_OR_DIGEST ...]

optional arguments:
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)

Verification inputs:
--certificate FILE, --cert FILE
--certificate, --cert FILE
The PEM-encoded certificate to verify against; not
used with multiple inputs (default: None)
--signature FILE The signature to verify against; not used with
Expand Down Expand Up @@ -248,13 +247,13 @@ usage: sigstore verify github [-h] [-v] [--certificate FILE]
[--ref REF]
FILE_OR_DIGEST [FILE_OR_DIGEST ...]

optional arguments:
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)

Verification inputs:
--certificate FILE, --cert FILE
--certificate, --cert FILE
The PEM-encoded certificate to verify against; not
used with multiple inputs (default: None)
--signature FILE The signature to verify against; not used with
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ extra:
- icon: fontawesome/brands/slack
link: https://sigstore.slack.com
- icon: fontawesome/brands/x-twitter
link: https://twitter.com/projectsigstore
link: https://twitter.com/projectsigstore
4 changes: 2 additions & 2 deletions sigstore/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ def _sign_common(
# 3) Interactive OAuth flow
identity: IdentityToken | None
if args.identity_token:
identity = IdentityToken(args.identity_token)
identity = IdentityToken(args.identity_token, args.client_id)
else:
identity = _get_identity(args, trust_config)

Expand Down Expand Up @@ -1185,7 +1185,7 @@ def _get_identity(

# Happy path: we've detected an ambient credential, so we can return early.
if token:
return IdentityToken(token)
return IdentityToken(token, args.client_id)

if args.oidc_issuer is not None:
issuer = Issuer(args.oidc_issuer)
Expand Down
16 changes: 9 additions & 7 deletions sigstore/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"https://oauth2.sigstage.dev/auth": "email",
"https://token.actions.githubusercontent.com": "sub",
}
_DEFAULT_AUDIENCE = "sigstore"

_DEFAULT_CLIENT_ID = "sigstore"


class _OpenIDConfiguration(BaseModel):
Expand All @@ -66,7 +67,7 @@ class IdentityToken:
a sensible subject, issuer, and audience for Sigstore purposes.
"""

def __init__(self, raw_token: str) -> None:
def __init__(self, raw_token: str, client_id: str) -> None:
"""
Create a new `IdentityToken` from the given OIDC token.
"""
Expand All @@ -90,7 +91,7 @@ def __init__(self, raw_token: str) -> None:
# See: https://openid.net/specs/openid-connect-basic-1_0.html#IDToken
"require": ["aud", "sub", "iat", "exp", "iss"],
},
audience=_DEFAULT_AUDIENCE,
audience=client_id,
# NOTE: This leeway shouldn't be strictly necessary, but is
# included to preempt any (small) skew between the host
# and the originating IdP.
Expand Down Expand Up @@ -270,7 +271,7 @@ def __init__(self, base_url: str) -> None:

def identity_token( # nosec: B107
self,
client_id: str = "sigstore",
client_id: str = _DEFAULT_CLIENT_ID,
client_secret: str = "",
force_oob: bool = False,
) -> IdentityToken:
Expand Down Expand Up @@ -350,7 +351,7 @@ def identity_token( # nosec: B107
if token_error is not None:
raise IdentityError(f"Error response from token endpoint: {token_error}")

return IdentityToken(token_json["access_token"])
return IdentityToken(token_json["access_token"], client_id)


class IdentityError(Error):
Expand Down Expand Up @@ -402,9 +403,10 @@ def diagnostics(self) -> str:
"""


def detect_credential() -> Optional[str]:
def detect_credential(client_id: str = _DEFAULT_CLIENT_ID) -> Optional[str]:
"""Calls `id.detect_credential`, but wraps exceptions with our own exception type."""

try:
return cast(Optional[str], id.detect_credential(_DEFAULT_AUDIENCE))
return cast(Optional[str], id.detect_credential(client_id))
except id.IdentityError as exc:
IdentityError.raise_from_id(exc)
4 changes: 1 addition & 3 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
detect_credential,
)

from sigstore.oidc import _DEFAULT_AUDIENCE

_ASSETS = (Path(__file__).parent / "assets").resolve()
assert _ASSETS.is_dir()

Expand All @@ -44,7 +42,7 @@ def _has_oidc_id():
return True

try:
token = detect_credential(_DEFAULT_AUDIENCE)
token = detect_credential()
if token is None:
return False
except GitHubOidcPermissionCredentialError:
Expand Down
6 changes: 3 additions & 3 deletions test/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from sigstore._internal.trust import ClientTrustConfig
from sigstore._utils import sha256_digest
from sigstore.models import Bundle
from sigstore.oidc import _DEFAULT_AUDIENCE, IdentityToken
from sigstore.oidc import IdentityToken
from sigstore.sign import SigningContext
from sigstore.verify.verifier import Verifier

Expand Down Expand Up @@ -209,7 +209,7 @@ def ctx_cls():
token = os.getenv(f"SIGSTORE_IDENTITY_TOKEN_{env}")
if not token:
# If the variable is not defined, try getting an ambient token.
token = detect_credential(_DEFAULT_AUDIENCE)
token = detect_credential()

return ctx_cls, IdentityToken(token)

Expand All @@ -230,7 +230,7 @@ def signer():
token = os.getenv("SIGSTORE_IDENTITY_TOKEN_staging")
if not token:
# If the variable is not defined, try getting an ambient token.
token = detect_credential(_DEFAULT_AUDIENCE)
token = detect_credential()

return signer, verifier, IdentityToken(token)

Expand Down