PIA (Project Identity Authority) is an authentication broker for Eclipse Foundation projects. It allows projects to publish artifacts, and to proof that the artifact came from the project's build infrastructure, using OpenID Connect (OIDC).
In the initial use case, described in this document, PIA serves as intermediary between Eclipse build infrastructure (GitHub Actions, Jenkins Pipelines) and the Eclipse DependencyTrack registry. This enables project-level access control for SBOM uploads to DependencyTrack. Later, PIA may extend to other use cases that benefit from this authentication.
DependencyTrack lacks project-level access control:
- Any team with upload permissions can publish SBOMs for all projects
- Shared secrets management is complex and error-prone
- Workarounds exist but are restricted to GitHub Actions
PIA implements a "Trusted Publishing" architecture using OIDC to:
- Authenticate publishers using short-lived identity tokens
- Verify project ownership through Identity Provider claims
- Eliminate need for long-lived credentials and manual secrets management between build infrastructure and DependencyTrack
- Support multiple build platforms (GitHub Actions, Jenkins, extensible to others)
- Provide a scalable architecture (extensible to other artifacts and registries)
sequenceDiagram
participant Publisher as Publisher<br/>(GHA, Jenkins Pipeline)
participant IdP as Identity Provider<br/>(GitHub, Jenkins Controller)
participant PIA as PIA<br/>
participant dp as DependencyTrack<br/>
Publisher->>IdP: 1. Request ID token
IdP->>Publisher: 2. Return ID token
Publisher->>PIA: 3. Post SBOM + token
PIA->>IdP: 4. Request OIDC config
IdP->>PIA: 5. Return OIDC config
PIA->>IdP: 6. Request JWKs
IdP->>PIA: 7. Return JWKs
PIA->>PIA: 8. Authenticate
Note over PIA: - Verify token<br/>- Match claims with projects settings
PIA->>dp: 9. Upload SBOM
dp-->>PIA: Success
PIA-->>Publisher: Success
- GitHub Action workflow or Jenkins Pipeline
- Generates SBOMs
- Requests identity token from its Identity Provider
- Sends SBOM + token to PIA
- GitHub: Central OIDC provider (provided by GitHub)
- Jenkins: Per-instance OIDC Provider (enabled via plugin)
- Issues cryptographically signed JWT tokens with claims about publisher identity
- Serves OIDC configuration and public keys from well known location
- Provides REST API for SBOM upload
- Verifies identity tokens using OIDC discovery protocol
- Authenticates SBOM based on OIDC claims and internal projects settings
- Publishes SBOM to DependencyTrack on behalf of authenticated projects
- Receives SBOM from PIA after successful authentication
- Identity Provider trusts Publisher because:
- Each GitHub Publisher has a built-in auth token for the central GitHub Identity Provider (managed by GitHub)
- Each Jenkins Publisher has its own Identity Provider running on the same Jenkins instance (managed by Eclipse Foundation)
- PIA trusts Identity Provider because PIA has projects settings for Identity Provider (issuer) URLs (managed by Eclipse Foundation)
- DependencyTrack trusts PIA because PIA has an auth token for the DependencyTrack API (managed by Eclipse Foundation)
-
Token Request: Publisher requests ID token from Identity Provider
- GitHub: Uses
ACTIONS_ID_TOKEN_REQUEST_URLandACTIONS_ID_TOKEN_REQUEST_TOKEN - Jenkins: Uses credentials binding plugin with pre-configured credential
- GitHub: Uses
-
Token Issuance: Identity Provider returns JWT with minimal required claims:
iss: Issuer URL (identifies Identity Provider)exp: Expiration timestampiat: Issued At timestampaud: Audience (must match PIA's expected audience)- Platform-specific:
- GitHub:
repositoryidentifies project via repository owner and name - Jenkins:
issalso identifies project via URL path
- GitHub:
-
Publish Request: Publisher sends POST request to PIA:
Authorizationheader: Bearer token containing OIDC token (RFC6750)product_name: Name of product for which the SBOM is produced. This field is required by DependencyTrack to aggregate SBOMs by product within a project. We ask the Publisher to provide it, so that we don't have to parse the SBOM (seemetadata.component.name).product_version: Version of product for which the SBOM was produced. This field is required by DependencyTrack. We ask the Publisher to provide it, so that we don't have to parse the SBOM (seemetadata.component.version).bom: Base64-encoded CycloneDX JSON SBOMis_latest: Whether this SBOM should be marked as the latest version in DependencyTrack (optional, default:true)
-
Token Verification and Authentication: PIA verifies POST data token
- see 3.1.1. Token Verification and Authentication Flow
-
SBOM Publishing: PIA sends upload request to DependencyTrack:
Content-Type:application/jsonX-Api-Key: Use internally stored, DependencyTrack access tokenprojectName: Useproduct_namefrom POST dataprojectVersion: Useproduct_versionfrom POST data.parentUUID: Look up internallyautoCreate:true(creates new subcategory for newprojectName)isLatest: Useis_latestfrom POST databom: Usebomfrom POST data
- Basic POST Data Validation
- Validate basic POST data schema
- Issuer Validation
- Decode token without any verification
- Extract issuer
- Verify issuer is known
- Token Key Discovery
- Fetch OIDC configuration from
{issuer}/.well-known/openid-configuration - Extract
jwks_urifrom configuration - Fetch public keys from
jwks_uri - Match key using
kidclaim from token header
- Fetch OIDC configuration from
- Token Validation and Signature Verification
- Verify generally required claims are present
- Verify token issued at time and expiry
- Verify audience matches expected value
- Verify signature using RSA256
- Project Authorization
- Match verified token claims (issuer + required_claims) against all project configurations to find the authorized project
Accepts sbom uploads with OIDC authentication.
Request Headers:
Authorization: Bearer <JWT ID token>
Content-Type: application/json
Request Body:
{
"product_name": "string", // Eclipse product name
"product_version": "string", // Eclipse product version
"bom": "string", // CycloneDX JSON SBOM (base64-encoded)
"is_latest": true // Mark as latest version in DependencyTrack (optional, default: true)
}Response:
401 Unauthorized:- Invalid Authorization header format
- Invalid token
- Expired token
- Token verification error
- Issuer not allowed
- No matching project found for token claims
422 Unprocessable Entity: Missing Authorization header or invalid JSON502: DependencyTrack upload request failed*: Relay DependencyTrack status code
PIA requires settings for:
-
Application: Loaded from environment variables with
PIA_prefix:PIA_DEPENDENCY_TRACK_API_KEY: DependencyTrack API key (required)PIA_PROJECTS_PATH: Path to projects.yaml file (required)PIA_DEPENDENCY_TRACK_URL: DependencyTrack SBOM upload URL (default:https://sbom.eclipse.org/api/v1/bom)PIA_EXPECTED_AUDIENCE: Expected audience for OIDC tokens (default:pia.eclipse.org)
-
Projects: YAML file (e.g.
projects.yaml) containing a list of project configurations with allowed issuer, required claims, and DependencyTrack project UUID. Schema:- project_id: <project_id> issuer: <issuer_url> dt_parent_uuid: <uuid> # DependencyTrack project UUID required_claims: # Omit for Jenkins (issuer is enough) <claim_name>: <expected_value>
NOTE: Initial implementation uses static YAML; designed for future database migration.
- Web Framework: FastAPI (modern, has built-in model validation and API documentation)
- HTTP Client:
requestsfor OIDC discovery and DependencyTrack API calls - JWT Library:
PyJWTwithcryptographysupport- Handles token parsing, validation, and signature verification
- Includes
PyJWKClientfor JWKS key fetching
- Testing:
pytest - Linting:
ruff - Python Project Management:
uv
main.py: FastAPI app with:- Settings management for application and project settings
- Upload SBOM API endpoint implementing full authentication and DependencyTrack upload flow (section 3.1, items 4. and 5.)
oidc.py: OIDC token validation and signature verification using PyJWTdependencytrack.py: DependencyTrack API upload client for SBOMsmodels.py: Pydantic data models with validation and authentication:Project: Project settings instanceProjects: List of Project instancesPiaUploadPayload: Request model for PIA Upload SBOM API endpointDependencyTrackUploadPayload: Request model for DependencyTrack API
-
Settings Errors: Fail fast at startup with clear error
-
Authentication Errors:
- 422 for malformed POST data
- 401 for unknown issuer, token verification errors, or no matching project found for verified token claims
-
DependencyTrack Upload Errors: 502, if upload fails with an error, or HTTP status code from DependencyTrack
Log important events:
- Settings re-load
- Client connects
- Token verifications
- DependencyTrack uploads
- Errors
Metrics to track:
- Client connect attempts by IP
- Client connect attempts by
product_nameandproduct_version - Token verification time
- DependencyTrack upload time
- Total API response time
- Issuer and Project: Only trust allowed issuers and projects
- Expiration: Reject expired tokens
- Signature: Verify signature before trusting claims
- Algorithm: Only accept RS256 (Identity Provider restriction)
- Key Rotation: Refresh OIDC configs and JWKs
| Attack | Mitigation |
|---|---|
| Token replay | Short-lived tokens (5-10 min), do not re-use token (future work) |
| Issuer spoofing | HTTPS-only issuer URLs, strict URL validation |
| Token forgery | Cryptographic signature verification |
| Project impersonation | Claim-to-project validation |
| DoS via OIDC discovery | Cache OIDC discovery, rate limit requests (both future work) |
| Parsing vulnerabilities | Use minimal schema for unauthenticated POST data, use well-tested JWT library for token parsing |
- Store secrets securely in environment variables
- Never log tokens or credentials
- Rotate DependencyTrack credentials regularly
- All communication must use HTTPS:
- PIA API endpoint
- OIDC discovery URLs
- DependencyTrack API endpoint
Identity Provider:
- Issuer:
https://token.actions.githubusercontent.com - OIDC config path:
/.well-known/openid-configuration
Required Claims:
repository: identifies Eclipse project (e.g.eclipse-foo/baridentifies project Eclipse Foo)
Example Publisher workflow:
jobs:
publish:
permissions:
id-token: write
steps:
- name: Get ID token
id: token
run: |
ID_TOKEN=$(curl \
-H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pia.eclipse.org")
echo "ID_TOKEN=$ID_TOKEN" >> $GITHUB_OUTPUT
- name: Publish SBOM
run: |
curl -X POST https://pia.eclipse.org/v1/upload/sbom \
-H "Authorization: Bearer ${{ steps.token.outputs.ID_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"product_name": "...", "product_version": "...", "bom": "..."}'Identity Providers (one per project):
- Issuer:
https://ci.eclipse.org/<PROJECT NAME>/oidc - OIDC config path:
/.well-known/openid-configuration
Required Claims:
- The issuer also identifies the project (e.g.
https://ci.eclipse.org/eclipse-baz/oidcidentifies project Eclipse Baz)
Publisher Setup:
- Install plugins:
oidc-provider,credentials-binding - Configure per-project Jenkins instance URL
- Create
id-tokencredential- Set
GLOBALscope, to make tokens available to jobs - Set audience, to prevent cross-service token replay
- Set
Example Publisher Credential Setup:
credentials:
system:
domainCredentials:
- credentials:
- idToken:
id: id-token
scope: GLOBAL
audience: pia.eclipse.orgExample Publisher Pipeline:
pipeline {
agent any
environment {
ID_TOKEN = credentials('id-token')
}
stages {
stage('Publish') {
steps {
sh '''
curl -X POST https://pia.eclipse.org/v1/upload/sbom \
-H "Authorization: Bearer ${ID_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"product_name": "...", "product_version": "...", "bom": "..."}'
'''
}
}
}
}- Python 3.13.2+
- Production ASGI server (e.g., uvicorn)
- Reverse proxy for TLS termination and rate limiting
- Container deployment
Settings via:
- YAML file (
projects.yaml) for project/issuer settings - Environment variables (with
PIA_prefix) for application settings
Example projects.yaml:
- project_id: my-github-project
issuer: "https://token.actions.githubusercontent.com"
dt_parent_uuid: "12345678-1234-1234-1234-123456789abc"
required_claims:
repository: "eclipse-foo/my-github-project"
- project_id: my-jenkins-project
issuer: "https://ci.eclipse.org/my-jenkins-project/oidc"
dt_parent_uuid: "87654321-4321-4321-4321-cba987654321"Required environment variables:
PIA_DEPENDENCY_TRACK_API_KEY=<your-api-key>
PIA_PROJECTS_PATH=/path/to/projects.yaml
PIA_DEPENDENCY_TRACK_URL=https://sbom.eclipse.org/api/v1/bom # optional, has default
PIA_EXPECTED_AUDIENCE=pia.eclipse.org # optional, has default- Stateless: Can run multiple instances behind load balancer
- Rate limiting per project/IP at reverse proxy level, if needed
- Async processing, if needed
- Settings loading and validation (pydantic-settings)
- Model validation (pydantic models)
- Token verification and authentication logic with test tokens
- Error handling paths
- FastAPI endpoints using httpx async test client
- DependencyTrack upload with mock server
- Replace yaml-based projects settings with database
- Add caching for oidc config and jwks
- Add monitoring
- Make upload API async, if sync takes too long
- Protect against replayed tokens (PyPI uses "jti" claim to track used tokens)
- Consider product-level authentication
- Trusted Publishing (PyPI)
- GitHub OIDC Documentation
- Jenkins OIDC Provider Plugin
- PyJWT Documentation
- RFC6750 - OAuth 2.0 Bearer Token Usage
- End-to-end Proof of Concept
- Concept Document (restricted access)
{
"jti": "...",
"sub": "repo:octo-org/octo-repo:ref:refs/heads/main",
"aud": "pia.eclipse.org",
"ref": "refs/heads/main",
"sha": "...",
"repository": "octo-org/octo-repo",
"repository_owner": "octo-org",
"run_id": "...",
"run_number": "...",
"run_attempt": "...",
"actor": "octocat",
"workflow": "CI",
"head_ref": "",
"base_ref": "",
"event_name": "push",
"ref_type": "branch",
"job_workflow_ref": "octo-org/octo-repo/.github/workflows/ci.yml@refs/heads/main",
"iss": "https://token.actions.githubusercontent.com",
"nbf": 1632492000,
"exp": 1632492900,
"iat": 1632492000
}{
"aud": "pia.eclipse.org",
"build_number": 2,
"exp": 1765461015,
"iat": 1765457415,
"iss": "https://ci.eclipse.org/my-project/oidc",
"sub": "https://ci.eclipse.org/my-project/job/oidc-upload-demo/"
}