From b2d4ab8ed8d3c6f0a8f1f47c901334cde3f0a991 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sat, 30 Dec 2023 20:37:45 -0500 Subject: [PATCH 1/7] oidc-exchange: specialize error on PRs from forks --- oidc-exchange.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/oidc-exchange.py b/oidc-exchange.py index fb1df00d..9725ad70 100644 --- a/oidc-exchange.py +++ b/oidc-exchange.py @@ -47,6 +47,19 @@ Learn more at https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings. """ +# Specialization of the token retrieval failure case, when we know that +# the failure cause is use within a third-party PR. +_TOKEN_RETRIEVAL_FAILED_3P_PR_MESSAGE = """ +OpenID Connect token retrieval failed: {identity_error} + +The workflow context indicates that this action was called from a +pull request on a fork. GitHub doesn't give these workflows OIDC permissions, +even if `id-token: write` is explicitly configured. + +To fix this, change your publishing workflow to use an event that +forks of your repository cannot trigger (such as tag or release creation). +""" + # Rendered if the package index refuses the given OIDC token. _SERVER_REFUSED_TOKEN_EXCHANGE_MESSAGE = """ Token request failed: the server refused the request for the following reasons: @@ -162,6 +175,27 @@ def _get(name: str) -> str: # noqa: WPS430 ) +def event_is_third_party_pr() -> bool: + # Non-`pull_request` events cannot be from third-party PRs. + if os.getenv("GITHUB_EVENT_NAME") != "pull_request": + return False + + if event_path := os.getenv("GITHUB_EVENT_PATH"): + try: + event = json.loads(Path(event_path).read_text()) + try: + return event["pull_request"]["head"]["repo"]["fork"] + except KeyError: + return False + except json.JSONDecodeError: + debug("unexpected: GITHUB_EVENT_PATH does not contain valid JSON") + return False + + # No GITHUB_EVENT_PATH indicates a weird GitHub or runner bug. + debug("unexpected: no GITHUB_EVENT_PATH to check") + return False + + repository_url = get_normalized_input("repository-url") repository_domain = urlparse(repository_url).netloc token_exchange_url = f"https://{repository_domain}/_/oidc/github/mint-token" @@ -179,7 +213,10 @@ def _get(name: str) -> str: # noqa: WPS430 try: oidc_token = id.detect_credential(audience=oidc_audience) except id.IdentityError as identity_error: - die(_TOKEN_RETRIEVAL_FAILED_MESSAGE.format(identity_error=identity_error)) + if event_is_third_party_pr(): + die(_TOKEN_RETRIEVAL_FAILED_3P_PR_MESSAGE.format(identity_error=identity_error)) + else: + die(_TOKEN_RETRIEVAL_FAILED_MESSAGE.format(identity_error=identity_error)) # Now we can do the actual token exchange. mint_token_resp = requests.post( From d2748ca0e2ea746633292c18598729204a5d07dd Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sat, 30 Dec 2023 20:42:24 -0500 Subject: [PATCH 2/7] oidc-exchange: lintage --- oidc-exchange.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/oidc-exchange.py b/oidc-exchange.py index 9725ad70..87cc32cb 100644 --- a/oidc-exchange.py +++ b/oidc-exchange.py @@ -49,7 +49,7 @@ # Specialization of the token retrieval failure case, when we know that # the failure cause is use within a third-party PR. -_TOKEN_RETRIEVAL_FAILED_3P_PR_MESSAGE = """ +_TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE = """ OpenID Connect token retrieval failed: {identity_error} The workflow context indicates that this action was called from a @@ -183,14 +183,15 @@ def event_is_third_party_pr() -> bool: if event_path := os.getenv("GITHUB_EVENT_PATH"): try: event = json.loads(Path(event_path).read_text()) - try: - return event["pull_request"]["head"]["repo"]["fork"] - except KeyError: - return False except json.JSONDecodeError: debug("unexpected: GITHUB_EVENT_PATH does not contain valid JSON") return False + try: + return event["pull_request"]["head"]["repo"]["fork"] + except KeyError: + return False + # No GITHUB_EVENT_PATH indicates a weird GitHub or runner bug. debug("unexpected: no GITHUB_EVENT_PATH to check") return False @@ -214,7 +215,11 @@ def event_is_third_party_pr() -> bool: oidc_token = id.detect_credential(audience=oidc_audience) except id.IdentityError as identity_error: if event_is_third_party_pr(): - die(_TOKEN_RETRIEVAL_FAILED_3P_PR_MESSAGE.format(identity_error=identity_error)) + die( + _TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE.format( + identity_error=identity_error + ) + ) else: die(_TOKEN_RETRIEVAL_FAILED_MESSAGE.format(identity_error=identity_error)) From a697e49504c3e2d8b03b9ead8abc1e114b2908b5 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sat, 30 Dec 2023 20:43:21 -0500 Subject: [PATCH 3/7] oidc-exchange: more lintage --- oidc-exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc-exchange.py b/oidc-exchange.py index 87cc32cb..5af2aaa8 100644 --- a/oidc-exchange.py +++ b/oidc-exchange.py @@ -182,7 +182,7 @@ def event_is_third_party_pr() -> bool: if event_path := os.getenv("GITHUB_EVENT_PATH"): try: - event = json.loads(Path(event_path).read_text()) + event = json.loads(Path(event_path).read_bytes()) except json.JSONDecodeError: debug("unexpected: GITHUB_EVENT_PATH does not contain valid JSON") return False From 25e0a31172c0bf80d46a82349ce93e402f88abfe Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sat, 30 Dec 2023 20:45:52 -0500 Subject: [PATCH 4/7] oidc-exchange: more percussive maintenance --- oidc-exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oidc-exchange.py b/oidc-exchange.py index 5af2aaa8..863e8d13 100644 --- a/oidc-exchange.py +++ b/oidc-exchange.py @@ -217,8 +217,8 @@ def event_is_third_party_pr() -> bool: if event_is_third_party_pr(): die( _TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE.format( - identity_error=identity_error - ) + identity_error=identity_error, + ), ) else: die(_TOKEN_RETRIEVAL_FAILED_MESSAGE.format(identity_error=identity_error)) From 3fa387ef2b0520c8349792edfa5abb73b027deff Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 5 Feb 2024 22:53:50 +0100 Subject: [PATCH 5/7] Update oidc-exchange.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- oidc-exchange.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oidc-exchange.py b/oidc-exchange.py index 863e8d13..2da984a3 100644 --- a/oidc-exchange.py +++ b/oidc-exchange.py @@ -57,7 +57,8 @@ even if `id-token: write` is explicitly configured. To fix this, change your publishing workflow to use an event that -forks of your repository cannot trigger (such as tag or release creation). +forks of your repository cannot trigger (such as tag or release +creation, or a manually triggered workflow dispatch). """ # Rendered if the package index refuses the given OIDC token. From a584d06c922958a99e586bcb9c3a375417f4ac40 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 5 Feb 2024 22:55:05 +0100 Subject: [PATCH 6/7] Update oidc-exchange.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- oidc-exchange.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/oidc-exchange.py b/oidc-exchange.py index 2da984a3..69faa029 100644 --- a/oidc-exchange.py +++ b/oidc-exchange.py @@ -215,14 +215,12 @@ def event_is_third_party_pr() -> bool: try: oidc_token = id.detect_credential(audience=oidc_audience) except id.IdentityError as identity_error: - if event_is_third_party_pr(): - die( - _TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE.format( - identity_error=identity_error, - ), - ) - else: - die(_TOKEN_RETRIEVAL_FAILED_MESSAGE.format(identity_error=identity_error)) + cause_msg_tmpl = ( + _TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE if event_is_third_party_pr() + else _TOKEN_RETRIEVAL_FAILED_MESSAGE + ) + for_cause_msg = cause_msg_tmpl.format(identity_error=identity_error) + die(for_cause_msg) # Now we can do the actual token exchange. mint_token_resp = requests.post( From 35f68b445e68258fe2d255421c250f2704e3c05b Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sun, 11 Feb 2024 22:51:55 +0000 Subject: [PATCH 7/7] Update oidc-exchange.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- oidc-exchange.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/oidc-exchange.py b/oidc-exchange.py index 69faa029..89a9724b 100644 --- a/oidc-exchange.py +++ b/oidc-exchange.py @@ -181,21 +181,22 @@ def event_is_third_party_pr() -> bool: if os.getenv("GITHUB_EVENT_NAME") != "pull_request": return False - if event_path := os.getenv("GITHUB_EVENT_PATH"): - try: - event = json.loads(Path(event_path).read_bytes()) - except json.JSONDecodeError: - debug("unexpected: GITHUB_EVENT_PATH does not contain valid JSON") - return False - - try: - return event["pull_request"]["head"]["repo"]["fork"] - except KeyError: - return False - - # No GITHUB_EVENT_PATH indicates a weird GitHub or runner bug. - debug("unexpected: no GITHUB_EVENT_PATH to check") - return False + event_path = os.getenv("GITHUB_EVENT_PATH") + if not event_path: + # No GITHUB_EVENT_PATH indicates a weird GitHub or runner bug. + debug("unexpected: no GITHUB_EVENT_PATH to check") + return False + + try: + event = json.loads(Path(event_path).read_bytes()) + except json.JSONDecodeError: + debug("unexpected: GITHUB_EVENT_PATH does not contain valid JSON") + return False + + try: + return event["pull_request"]["head"]["repo"]["fork"] + except KeyError: + return False repository_url = get_normalized_input("repository-url")