This is an automated email from the ASF dual-hosted git repository.
arm pushed a commit to branch jwtoken_multiple_sources
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/jwtoken_multiple_sources by
this push:
new d553c62 #504 - Add new ATR token to github workflows and validate
d553c62 is described below
commit d553c62cbff12fe6bf45eaa3166bae6746792473
Author: Alastair McFarlane <[email protected]>
AuthorDate: Tue Jan 27 10:18:58 2026 +0000
#504 - Add new ATR token to github workflows and validate
---
atr/api/__init__.py | 48 +++++------
atr/jwtoken.py | 225 +++++++++++++++++++++++++++++-----------------------
atr/tasks/gha.py | 11 ++-
3 files changed, 157 insertions(+), 127 deletions(-)
diff --git a/atr/api/__init__.py b/atr/api/__init__.py
index 09eb198..ef9840e 100644
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -253,7 +253,7 @@ async def committees_list() -> DictResponse:
@api.route("/distribute/ssh/register", methods=["POST"])
[email protected](token_types=["github"])
[email protected](tp=True, atr=False)
@quart_schema.validate_request(models.api.DistributeSshRegisterArgs)
async def distribute_ssh_register(data: models.api.DistributeSshRegisterArgs)
-> DictResponse:
"""
@@ -284,7 +284,7 @@ async def distribute_ssh_register(data:
models.api.DistributeSshRegisterArgs) ->
@api.route("/distribute/task/status", methods=["POST"])
[email protected](token_types=["github"])
[email protected](tp=True)
@quart_schema.validate_request(models.api.DistributeStatusUpdateArgs)
async def update_distribution_task_status(data:
models.api.DistributeStatusUpdateArgs) -> DictResponse:
"""
@@ -308,7 +308,7 @@ async def update_distribution_task_status(data:
models.api.DistributeStatusUpdat
@api.route("/distribution/record", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True, tp=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.DistributionRecordArgs)
@quart_schema.validate_response(models.api.DistributionRecordResults, 200)
@@ -347,7 +347,7 @@ async def distribution_record(data:
models.api.DistributionRecordArgs) -> DictRe
@api.route("/distribute/record_from_workflow", methods=["POST"])
[email protected](token_types=["github"])
[email protected](atr=False, tp=True)
@quart_schema.validate_request(models.api.DistributionRecordFromWorkflowArgs)
async def distribution_record_from_workflow(data:
models.api.DistributionRecordFromWorkflowArgs) -> DictResponse:
"""
@@ -385,7 +385,7 @@ async def distribution_record_from_workflow(data:
models.api.DistributionRecordF
@api.route("/ignore/add", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.IgnoreAddArgs)
@quart_schema.validate_response(models.api.IgnoreAddResults, 200)
@@ -414,7 +414,7 @@ async def ignore_add(data: models.api.IgnoreAddArgs) ->
DictResponse:
@api.route("/ignore/delete", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.IgnoreDeleteArgs)
@quart_schema.validate_response(models.api.IgnoreDeleteResults, 200)
@@ -479,7 +479,7 @@ async def jwt_create(data: models.api.JwtCreateArgs) ->
DictResponse:
@api.route("/key/add", methods=["POST"])
@rate_limiter.rate_limit(10, datetime.timedelta(hours=1))
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.KeyAddArgs)
@quart_schema.validate_response(models.api.KeyAddResults, 200)
@@ -512,7 +512,7 @@ async def key_add(data: models.api.KeyAddArgs) ->
DictResponse:
@api.route("/key/delete", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.KeyDeleteArgs)
@quart_schema.validate_response(models.api.KeyDeleteResults, 200)
@@ -566,7 +566,7 @@ async def key_get(fingerprint: str) -> DictResponse:
@api.route("/keys/upload", methods=["POST"])
@rate_limiter.rate_limit(10, datetime.timedelta(hours=1))
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.KeysUploadArgs)
@quart_schema.validate_response(models.api.KeysUploadResults, 200)
@@ -721,7 +721,7 @@ async def projects_list() -> DictResponse:
@api.route("/publisher/distribution/record", methods=["POST"])
[email protected](token_types=["github"])
[email protected](tp=True, atr=False)
@quart_schema.validate_request(models.api.PublisherDistributionRecordArgs)
async def publisher_distribution_record(data:
models.api.PublisherDistributionRecordArgs) -> DictResponse:
"""
@@ -772,7 +772,7 @@ async def publisher_distribution_record(data:
models.api.PublisherDistributionRe
@api.route("/publisher/release/announce", methods=["POST"])
[email protected](token_types=["github"])
[email protected](tp=True, atr=False)
@quart_schema.validate_request(models.api.PublisherReleaseAnnounceArgs)
async def publisher_release_announce(data:
models.api.PublisherReleaseAnnounceArgs) -> DictResponse:
"""
@@ -809,7 +809,7 @@ async def publisher_release_announce(data:
models.api.PublisherReleaseAnnounceAr
@api.route("/publisher/ssh/register", methods=["POST"])
[email protected](token_types=["github"])
[email protected](tp=True, atr=False)
@rate_limiter.rate_limit(10, datetime.timedelta(hours=1))
@quart_schema.validate_request(models.api.PublisherSshRegisterArgs)
async def publisher_ssh_register(data: models.api.PublisherSshRegisterArgs) ->
DictResponse:
@@ -840,7 +840,7 @@ async def publisher_ssh_register(data:
models.api.PublisherSshRegisterArgs) -> D
@api.route("/publisher/vote/resolve", methods=["POST"])
[email protected](token_types=["github"])
[email protected](tp=True, atr=False)
@quart_schema.validate_request(models.api.PublisherVoteResolveArgs)
async def publisher_vote_resolve(data: models.api.PublisherVoteResolveArgs) ->
DictResponse:
"""
@@ -872,7 +872,7 @@ async def publisher_vote_resolve(data:
models.api.PublisherVoteResolveArgs) -> D
@api.route("/release/announce", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.ReleaseAnnounceArgs)
@quart_schema.validate_response(models.api.ReleaseAnnounceResults, 201)
@@ -910,7 +910,7 @@ async def release_announce(data:
models.api.ReleaseAnnounceArgs) -> DictResponse
@api.route("/release/create", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.ReleaseCreateArgs)
@quart_schema.validate_response(models.api.ReleaseCreateResults, 201)
@@ -937,7 +937,7 @@ async def release_create(data:
models.api.ReleaseCreateArgs) -> DictResponse:
# TODO: Duplicates the below
@api.route("/release/delete", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.ReleaseDeleteArgs)
@quart_schema.validate_response(models.api.ReleaseDeleteResults, 200)
@@ -1020,7 +1020,7 @@ async def release_revisions(project: str, version: str)
-> DictResponse:
@api.route("/release/upload", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.ReleaseUploadArgs)
@quart_schema.validate_response(models.api.ReleaseUploadResults, 201)
@@ -1088,7 +1088,7 @@ async def releases_list(query_args:
models.api.ReleasesListQuery) -> DictRespons
@api.route("/signature/provenance", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.SignatureProvenanceArgs)
@quart_schema.validate_response(models.api.SignatureProvenanceResults, 200)
@@ -1152,7 +1152,7 @@ async def signature_provenance(data:
models.api.SignatureProvenanceArgs) -> Dict
@api.route("/ssh-key/add", methods=["POST"])
@rate_limiter.rate_limit(10, datetime.timedelta(hours=1))
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.SshKeyAddArgs)
@quart_schema.validate_response(models.api.SshKeyAddResults, 201)
@@ -1174,7 +1174,7 @@ async def ssh_key_add(data: models.api.SshKeyAddArgs) ->
DictResponse:
@api.route("/ssh-key/delete", methods=["POST"])
@rate_limiter.rate_limit(10, datetime.timedelta(hours=1))
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.SshKeyDeleteArgs)
@quart_schema.validate_response(models.api.SshKeyDeleteResults, 201)
@@ -1253,7 +1253,7 @@ async def tasks_list(query_args:
models.api.TasksListQuery) -> DictResponse:
@api.route("/user/info")
@rate_limiter.rate_limit(10, datetime.timedelta(hours=1))
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_response(models.api.UserInfoResults, 200)
async def user_info() -> DictResponse:
@@ -1311,7 +1311,7 @@ async def users_list() -> DictResponse:
# TODO: Add endpoints to allow users to vote
@api.route("/vote/resolve", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.VoteResolveArgs)
@quart_schema.validate_response(models.api.VoteResolveResults, 200)
@@ -1346,7 +1346,7 @@ async def vote_resolve(data: models.api.VoteResolveArgs)
-> DictResponse:
@api.route("/vote/start", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.VoteStartArgs)
@quart_schema.validate_response(models.api.VoteStartResults, 201)
@@ -1389,7 +1389,7 @@ async def vote_start(data: models.api.VoteStartArgs) ->
DictResponse:
@api.route("/vote/tabulate", methods=["POST"])
[email protected](token_types=["atr"])
[email protected](atr=True)
@quart_schema.security_scheme([{"BearerAuth": []}])
@quart_schema.validate_request(models.api.VoteTabulateArgs)
@quart_schema.validate_response(models.api.VoteTabulateResults, 200)
diff --git a/atr/jwtoken.py b/atr/jwtoken.py
index dc67ff8..428547c 100644
--- a/atr/jwtoken.py
+++ b/atr/jwtoken.py
@@ -34,23 +34,29 @@ import atr.log as log
_ALGORITHM: Final[str] = "HS256"
_ATR_JWT_AUDIENCE: Final[str] = "atr-api-pat-test-v1"
_ATR_JWT_ISSUER: Final[str] = f"https://{config.get().APP_HOST}/"
-_GITHUB_OIDC_AUDIENCE: Final[str] = "atr-test-v1"
-_GITHUB_OIDC_EXPECTED: Final[dict[str, str]] = {
- "enterprise": "the-asf",
- "enterprise_id": "212555",
- "repository_owner": "apache",
- "runner_environment": "github-hosted",
-}
-_GITHUB_OIDC_ISSUER: Final[str] = "https://token.actions.githubusercontent.com"
-_GITHUB_TOKEN_FIELD: Final[str] = "jwt"
_GITHUB_TRUSTED_ROLE_NID: Final[int] = 254436773
_JWT_SECRET_KEY: Final[str] = config.get().JWT_SECRET_KEY
+# Trusted Publishers configuration
+_TRUSTED_PUBLISHERS: Final[dict[str, dict[str, Any]]] = {
+ "https://token.actions.githubusercontent.com": {
+ "name": "github",
+ "audience": "atr-test-v1",
+ "algorithm": "RS256",
+ "expected_claims": {
+ "enterprise": "the-asf",
+ "enterprise_id": "212555",
+ "repository_owner": "apache",
+ "runner_environment": "github-hosted",
+ },
+ },
+}
+
if TYPE_CHECKING:
from collections.abc import Awaitable, Callable, Coroutine
-def issue(uid: str, *, ttl: int = 90 * 60) -> str:
+def issue(uid: str, *, ttl: int = 90 * 60, claims: dict[str, str] | None =
None) -> str:
now = datetime.datetime.now(tz=datetime.UTC)
payload = {
"sub": uid,
@@ -60,31 +66,49 @@ def issue(uid: str, *, ttl: int = 90 * 60) -> str:
"exp": now + datetime.timedelta(seconds=ttl),
"jti": secrets.token_hex(128 // 8),
}
+ if claims:
+ payload.update(claims)
return jwt.encode(payload, _JWT_SECRET_KEY, algorithm=_ALGORITHM)
-def require[**P, R](*, token_types=None) -> Callable[[Callable[P,
Coroutine[Any, Any, R]]], Callable[P, Awaitable[R]]]:
- if token_types is None:
- token_types = ["atr"]
+def require[**P, R](
+ *, atr: bool = False, tp: bool = False
+) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P,
Awaitable[R]]]:
+ """
+ Require JWT authentication.
+
+ Args:
+ atr: Accept ATR tokens (default True)
+ tp: Accept Trusted Publisher tokens (default False)
+ """
def decorator(func: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P,
Awaitable[R]]:
@functools.wraps(func)
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
- errors: list[str] = []
- claims = None
- gh_asf_uid = None
-
- for token_type in token_types:
- claims, gh_asf_uid = await _try_verify_token(token_type,
quart.request, errors)
- if claims is not None:
- break
+ # Always extract token from Authorization header
+ token, secondary_token = _extract_bearer_tokens(quart.request)
+
+ # Decode without verification to check the issuer
+ try:
+ unverified = unverified_header_and_payload(token)
+ issuer = unverified["payload"].get("iss")
+ except Exception as exc:
+ raise base.ASFQuartException(f"Failed to decode token: {exc}",
errorcode=401) from exc
+
+ if not issuer:
+ raise base.ASFQuartException("Token missing 'iss' claim",
errorcode=401)
+
+ try:
+ claims, asf_uid = await _verify_tokens(issuer, token,
secondary_token, atr, tp)
+ quart.g.jwt_claims = claims
+ quart.g.github_asf_uid = asf_uid
+ except jwt.ExpiredSignatureError as exc:
+ raise base.ASFQuartException("Token has expired",
errorcode=401) from exc
+ except jwt.InvalidTokenError as exc:
+ raise base.ASFQuartException("Invalid JWT format",
errorcode=401) from exc
+ except jwt.PyJWTError as exc:
+ raise base.ASFQuartException(f"Invalid JWT: {exc}",
errorcode=401) from exc
- if claims is None:
- error_msg = "; ".join(errors) if errors else "Authentication
required"
- raise base.ASFQuartException(error_msg, errorcode=401)
-
- quart.g.jwt_claims = claims
- quart.g.github_asf_uid = gh_asf_uid
return await func(*args, **kwargs)
return wrapper
@@ -114,11 +138,12 @@ def verify(token: str) -> dict[str, Any]:
)
-async def verify_github_oidc(token: str) -> dict[str, Any]:
+async def _verify_oidc_token(token: str, issuer: str, audience: str,
algorithm: str) -> dict[str, Any]:
+ """Verify an OIDC token using JWKS."""
try:
async with aiohttp.ClientSession() as session:
r = await session.get(
- f"{_GITHUB_OIDC_ISSUER}/.well-known/openid-configuration",
+ f"{issuer}/.well-known/openid-configuration",
timeout=aiohttp.ClientTimeout(total=10),
)
r.raise_for_status()
@@ -126,106 +151,104 @@ async def verify_github_oidc(token: str) -> dict[str,
Any]:
except aiohttp.ClientSSLError as exc:
log.error(f"TLS failure fetching OIDC config: {exc}")
raise base.ASFQuartException(
- f"TLS verification failed for GitHub OIDC endpoint: {exc}",
+ f"TLS verification failed for OIDC endpoint: {exc}",
errorcode=502,
) from exc
except aiohttp.ClientConnectionError as exc:
- log.error(f"Failed to connect to GitHub OIDC endpoint: {exc}")
+ log.error(f"Failed to connect to OIDC endpoint: {exc}")
raise base.ASFQuartException(
- f"Failed to connect to GitHub OIDC endpoint: {exc}",
+ f"Failed to connect to OIDC endpoint: {exc}",
errorcode=502,
) from exc
except aiohttp.ClientResponseError as exc:
- log.error(f"GitHub OIDC endpoint returned HTTP {exc.status}:
{exc.message}")
+ log.error(f"OIDC endpoint returned HTTP {exc.status}: {exc.message}")
raise base.ASFQuartException(
- f"GitHub OIDC endpoint returned HTTP {exc.status}: {exc.message}",
+ f"OIDC endpoint returned HTTP {exc.status}: {exc.message}",
errorcode=502,
) from exc
except (aiohttp.ServerTimeoutError, aiohttp.ClientError) as exc:
log.warning(f"Failed to fetch OIDC config: {exc}")
- jwks_uri = f"{_GITHUB_OIDC_ISSUER}/.well-known/jwks"
+ jwks_uri = f"{issuer}/.well-known/jwks"
jwks_client = jwt.PyJWKClient(jwks_uri)
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
key=signing_key.key,
- algorithms=["RS256"],
- audience=_GITHUB_OIDC_AUDIENCE,
- issuer=_GITHUB_OIDC_ISSUER,
+ algorithms=[algorithm],
+ audience=audience,
+ issuer=issuer,
options={"require": ["exp", "iat"]},
)
- for key, value in _GITHUB_OIDC_EXPECTED.items():
- if payload[key] != value:
- raise base.ASFQuartException(
- f"GitHub OIDC payload mismatch: {key} = {payload[key]} !=
{value}",
- errorcode=401,
- )
- # del payload["actor_id"]
- del payload["repository_id"]
- del payload["repository_owner_id"]
- del payload["run_id"]
return payload
-def _extract_bearer_token(request: quart.Request) -> str:
- header = request.headers.get("Authorization", "")
- scheme, _, token = header.partition(" ")
- if (scheme.lower() != "bearer") or (not token):
- raise base.ASFQuartException(
- "Authentication required. Please provide a valid Bearer token in
the Authorization header", errorcode=401
- )
- return token
+async def _verify_tokens(
+ issuer, token: str, secondary_token: str, atr: bool, tp: bool
+) -> tuple[dict[str, Any], str | None]:
+ """Route to appropriate verifier based on issuer and verify tokens"""
+ if issuer == _ATR_JWT_ISSUER:
+ if not atr:
+ raise base.ASFQuartException("ATR tokens not accepted for this
endpoint", errorcode=401)
+ claims = verify(token)
+ return claims, None
+ elif issuer in _TRUSTED_PUBLISHERS:
+ if not tp:
+ raise base.ASFQuartException("Trusted Publisher tokens not
accepted for this endpoint", errorcode=401)
+ claims, asf_uid = await _verify_trusted_publisher(token, issuer,
secondary_token)
+ return claims, asf_uid
+ else:
+ raise base.ASFQuartException(f"Unknown token issuer: {issuer}",
errorcode=401)
-async def _extract_token_from_body(request: quart.Request, field: str) ->
tuple[str, dict[str, Any]]:
- try:
- body = await request.get_json()
- except Exception as exc:
- raise base.ASFQuartException("Invalid JSON in request body",
errorcode=400) from exc
+async def _verify_trusted_publisher(token: str, issuer: str, secondary_token:
str) -> tuple[dict[str, Any], str | None]:
+ """Verify a Trusted Publisher token and return claims and ASF UID."""
+ publisher_config = _TRUSTED_PUBLISHERS[issuer]
- if not body:
- raise base.ASFQuartException("Request body is required", errorcode=400)
+ # Verify the token using OIDC
+ payload = await _verify_oidc_token(token, issuer,
publisher_config["audience"], publisher_config["algorithm"])
- token = body.get(field)
- if not token:
- raise base.ASFQuartException(f"Missing '{field}' field in request
body", errorcode=400)
+ # Check expected claims
+ for key, value in publisher_config["expected_claims"].items():
+ if payload.get(key) != value:
+ raise base.ASFQuartException(
+ f"Trusted Publisher payload mismatch: {key} =
{payload.get(key)} != {value}",
+ errorcode=401,
+ )
- if not isinstance(token, str):
- raise base.ASFQuartException(f"'{field}' must be a string",
errorcode=400)
+ # Clean up unnecessary fields
+ payload.pop("repository_id", None)
+ payload.pop("repository_owner_id", None)
+ payload.pop("run_id", None)
+
+ # Determine ASF UID
+ # If actor_id is the trusted role, check for a secondary token to find the
ASF UID
+ # Otherwise, look up the ASF UID from GitHub ID
+ actor_id = payload.get("actor_id")
+ if actor_id and int(actor_id) != _GITHUB_TRUSTED_ROLE_NID:
+ asf_uid = await ldap.github_to_apache(actor_id)
+ else:
+ if secondary_token and len(secondary_token) > 0:
+ asf_uid = _validate_workflow_token(secondary_token,
payload["workflow_ref"])
+ else:
+ asf_uid = None
- return token, body
+ return payload, asf_uid
-async def _try_verify_token(
- token_type: str, request: quart.Request, errors: list[str]
-) -> tuple[dict[str, Any] | None, str | None] | tuple[None, None]:
- try:
- if token_type == "atr":
- token = _extract_bearer_token(request)
- return verify(token), None
- if token_type == "github":
- token, body = await _extract_token_from_body(request,
_GITHUB_TOKEN_FIELD)
- github_payload, asf_uid = await
_validate_trusted_jwt(body.get("publisher", ""), token)
- return github_payload, asf_uid
- raise RuntimeError(f"Invalid token type: {token_type}")
- except jwt.ExpiredSignatureError:
- errors.append(f"{token_type}: Token has expired")
- except jwt.InvalidTokenError:
- errors.append(f"{token_type}: Invalid Bearer JWT format")
- except jwt.PyJWTError as exc:
- errors.append(f"{token_type}: Invalid Bearer JWT: {exc!s}")
- except Exception as exc:
- errors.append(f"{token_type}: {exc!s}")
- return None, None
-
-
-async def _validate_trusted_jwt(publisher: str, token: str) -> tuple[dict[str,
Any], str | None]:
- if publisher != "github":
- raise jwt.InvalidTokenError(f"Publisher {publisher} not supported")
- payload = await verify_github_oidc(token)
- if int(payload["actor_id"]) != _GITHUB_TRUSTED_ROLE_NID:
- asf_uid = await ldap.github_to_apache(payload["actor_id"])
- else:
- asf_uid = None
- return payload, asf_uid
+def _validate_workflow_token(token: str, workflow: str) -> str:
+ payload = verify(token)
+ if payload.get("workflow_ref", "") != workflow:
+ raise base.ASFQuartException("Invalid workflow token", errorcode=401)
+ return payload["sub"]
+
+
+def _extract_bearer_tokens(request: quart.Request) -> tuple[str, str]:
+ header = request.headers.get("Authorization", "")
+ secondary_token = request.headers.get("ATR-Auth", "")
+ scheme, _, token = header.partition(" ")
+ if (scheme.lower() != "bearer") or (not token):
+ raise base.ASFQuartException(
+ "Authentication required. Please provide a valid Bearer token in
the Authorization header", errorcode=401
+ )
+ return token, secondary_token
diff --git a/atr/tasks/gha.py b/atr/tasks/gha.py
index 5e3a628..0776709 100644
--- a/atr/tasks/gha.py
+++ b/atr/tasks/gha.py
@@ -26,6 +26,7 @@ import pydantic
import atr.config as config
import atr.db as db
+import atr.jwtoken as jwtoken
import atr.log as log
import atr.models.results as results
import atr.models.schema as schema
@@ -75,11 +76,17 @@ async def trigger_workflow(args: DistributionWorkflow, *,
task_id: int | None =
except KeyError:
_fail(f"Invalid platform: {args.platform}")
workflow = f"distribute-{sql_platform.value.gh_slug}{'-stg' if
args.staging else ''}.yml"
+ ref = "main"
+ token = jwtoken.issue(
+ args.asf_uid,
+ ttl=60 * 60,
+ claims={"atr-workflow":
f"apache/tooling-actions/.github/workflows/{workflow}@refs/heads/{ref}"},
+ )
payload = {
- "ref": "main",
+ "ref": ref,
"inputs": {
"atr-id": unique_id,
- "asf-uid": args.asf_uid,
+ "atr-token": token,
"project": args.project_name,
"phase": args.phase,
"version": args.version_name,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]