OpenID Connect with Identity Federation
Advanced
Overview
Package Manager’s identity federation feature enables seamless integration with external systems that use OpenID Connect tokens for authentication. This powerful capability allows Package Manager to accept and validate tokens issued by external identity providers, eliminating the need for separate credential management across different systems.
Common integration scenarios include:
- GitHub Actions workflows accessing Package Manager repositories during CI/CD pipelines
- External CI/CD systems (GitLab CI, Jenkins, Azure DevOps) authenticating with their own identity providers
- Service accounts from partner organizations or external systems
- Cross-platform integrations where applications authenticate through multiple identity providers
- Automated deployment pipelines requiring secure package access
Identity federation differs from standard OpenID Connect authentication in that it focuses on API access rather than user login sessions. While OpenID Connect authentication allows users to sign in through external providers, identity federation allows Package Manager to trust and validate tokens that external systems have already obtained from their own identity providers.
This approach provides enhanced security and operational efficiency by:
- Eliminating the need to manage separate credentials for each system
- Leveraging existing identity infrastructure investments
- Providing fine-grained access control through scope mapping
- Supporting RFC 8693 token exchange for secure token conversion
- Enabling audit trails across integrated systems
Package Manager CLI Integration
The Package Manager CLI (rspm
) provides built-in support for identity federation through the PACKAGEMANAGER_IDENTITY_TOKEN_FILE
environment variable. When this environment variable is set and points to a file containing an external OpenID Connect token, the CLI will automatically:
- Read the external token from the specified file
- Perform RFC 8693 token exchange to obtain a Package Manager token
- Use the exchanged token for authentication
- Execute the requested command
This seamless integration makes it easy to use Package Manager from CI/CD environments that already provide OpenID Connect tokens. For detailed examples of using the CLI with identity federation, see:
How Identity Federation Works
- An external system (like GitHub Actions) obtains an OpenID Connect token from its own identity provider
- The system exchanges the external token for a Package Manager token using RFC 8693 token exchange
- The Package Manager token is used to authenticate API requests
- Package Manager validates requests and grants access based on the configured scope mappings
Token Exchange (RFC 8693)
Package Manager implements RFC 8693 OAuth 2.0 Token Exchange to convert external OpenID Connect tokens into Package Manager access tokens. This is necessary because you cannot use external ID tokens directly as authorization headers - they must first be exchanged for Package Manager tokens.
Token Exchange Endpoint
The token exchange endpoint is available at https://HOST:PORT/__api__/token
and accepts the following parameters:
grant_type
: Must beurn:ietf:params:oauth:grant-type:token-exchange
subject_token
: The external OpenID Connect token to exchangesubject_token_type
: Must beurn:ietf:params:oauth:token-type:id_token
scope
: Required but can be any value (scopes are determined by identity federation configuration)
Example Token Exchange
# Exchange an external OIDC token for a Package Manager token
curl -X POST 'https://packagemanager.example.com/__api__/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode "subject_token=$EXTERNAL_TOKEN" \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:id_token'
# Response will contain the Package Manager access token
{
"access_token": "<ppm_token_here>",
"issued_token_type":"urn:ietf:params:oauth:token-type:jwt",
"token_type": "Bearer",
"expires_in": 3600
}
Configuration Overview
Identity federation is configured using IdentityFederation
sections in the Package Manager configuration. Each section defines a separate identity provider that Package Manager should trust.
Basic Configuration Example
/etc/rstudio-pm/rstudio-pm.gcfg
; Example: Trust GitHub Actions tokens
[IdentityFederation "github-actions"]
Issuer = "https://token.actions.githubusercontent.com"
Audience = "https://github.com/your-org"
Subject = "repo:your-org/your-repo:.*"
Scope = "repos:read:*"
; Example: Trust tokens from another identity provider
[IdentityFederation "external-ci"]
Issuer = "https://auth.company.com"
Audience = "https://build.example.com"
Subject = "^service-account-.*"
Scope = "sources:write:*"
GitHub Actions Integration
A common use case for identity federation is allowing GitHub Actions workflows to access Package Manager repositories. Here’s how to configure this:
Step 1: Configure Identity Federation for GitHub
/etc/rstudio-pm/rstudio-pm.gcfg
[IdentityFederation "github-actions"]
Issuer = "https://token.actions.githubusercontent.com"
Audience = "https://github.com/your-org"
Subject = "repo:your-org/your-repo:pull_request"
Scope = "repos:read:*"
Scope = "sources:write:*"
; Optional: Enable logging for troubleshooting
Logging = true
The Subject
field should match the expected subject claim pattern from GitHub Actions tokens. Common patterns include:
repo:org/repo:ref:refs/heads/main
for main branchrepo:org/repo:pull_request
for pull requestsrepo:org/repo:.*
for any ref in the repository
Step 2: Configure GitHub Actions Workflow
Here’s a complete example of a GitHub Actions workflow that uses identity federation to access Package Manager:
name: Package Manager Integration
on:
pull_request:
branches: ['**']
workflow_dispatch:
permissions:
id-token: write # Required for requesting OIDC tokens
contents: read # Required for actions/checkout
jobs:
test-package-manager:
name: Test Package Manager Access
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Fetch OIDC Token
id: fetch-token
run: |
set -eo pipefail
# Request OIDC token from GitHub
RAW_TOKEN_JSON=$(curl -sf -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://github.com/your-org")
# Extract token value and save securely
echo "${RAW_TOKEN_JSON}" | jq -r '.value' > ${{ runner.temp }}/github_token
echo "GitHub OIDC token obtained successfully"
- name: Exchange Token with Package Manager
id: exchange-token
run: |
# Read the GitHub OIDC token
GITHUB_TOKEN=$(cat ${{ runner.temp }}/github_token)
# Exchange for Package Manager token
RESPONSE=$(curl -sf -X POST \
'https://packagemanager.example.com/__api__/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode "subject_token=$GITHUB_TOKEN" \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:id_token')
# Extract Package Manager token
PPM_TOKEN=$(echo "$RESPONSE" | jq -r '.access_token')
echo "::add-mask::$PPM_TOKEN"
echo "ppm_token=$PPM_TOKEN" >> $GITHUB_OUTPUT
- name: Install Python packages from Package Manager
run: |
# Create virtual environment
python -m venv venv
source venv/bin/activate
# Configure pip to use Package Manager with token authentication
export PIP_INDEX_URL="https://__token__:${{ steps.exchange-token.outputs.ppm_token }}@packagemanager.example.com/pypi/latest/simple/"
# Install packages
pip install requests numpy pandas
- name: Upload package to Package Manager
if: github.event_name == 'push'
run: |
# Install twine for uploads
pip install twine
# Configure twine with Package Manager token
export TWINE_REPOSITORY_URL="https://packagemanager.example.com/upload/pypi/local-source"
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="${{ steps.exchange-token.outputs.ppm_token }}"
# Upload package (assuming package is built) twine upload dist/*.tar.gz
Step 3: Alternative - Using Environment Variables
You can also configure Package Manager authentication using environment variables:
- name: Set Package Manager Environment
run: |
# For tools that support Package Manager environment variables
echo "PACKAGEMANAGER_IDENTITY_TOKEN_FILE=${{ runner.temp }}/github_token" >> $GITHUB_ENV
echo "PACKAGEMANAGER_ADDRESS=https://packagemanager.example.com" >> $GITHUB_ENV
- name: Use token exchange with environment variables
run: |
# Create and activate a Python virtual environment.
python -m venv venv
source venv/bin/activate
# Install necessary python packages
pip install --upgrade pip
pip install twine
# Install the package
echo "Installing posit-keyring..."
pip install posit-keyring
# Check keyring is installed and pointing to the correct backend
echo "Checking keyring backend..."
which keyring
keyring --list-backends
# Set environment variables for the tests
echo "Setting necessary environment variables..."
export PIP_INDEX_URL="https://__token__@packagemanager.example.com/pypi-auth/latest/simple/"
export TWINE_REPOSITORY_URL="https://packagemanager.example.com/upload/pypi/local-python"
export TWINE_USERNAME="__token__"
# Run pip test against Package Manager
echo "Testing pip install..."
pip install chatlas
# Run twine test against Package Manager
echo "Testing twine upload..." twine upload tests/data/posit_test_package_example-1.0.0.tar.gz
Advanced Configuration Options
Filtering by Subject
You can restrict which tokens are accepted based on the subject claim:
/etc/rstudio-pm/rstudio-pm.gcfg
; Only accept tokens for specific service accounts
[IdentityFederation "service-accounts"]
Issuer = "https://auth.company.com"
Audience = "some-audience"
Subject = "^service-account-(ci|deployment)$"
Scope = "sources:write:*"
Group and Role-Based Scope Mapping
Similar to OpenID Connect authentication, you can map groups and roles from identity federation tokens to Package Manager scopes:
/etc/rstudio-pm/rstudio-pm.gcfg
[IdentityFederation "github-actions"]
Issuer = "https://token.actions.githubusercontent.com"
Audience = "https://github.com/your-org"
Subject = ".*"
GroupsClaim = "repository_owner"
; Map repository owners to different scopes
[GroupToScopeMapping "data-science-team"]
Provider = "github-actions"
Scope = "repos:read:*"
Scope = "sources:write:*"
[GroupToScopeMapping "public-repos"]
Provider = "github-actions"
Scope = "repos:read:*"
Multiple Identity Providers
You can configure multiple identity federation providers:
/etc/rstudio-pm/rstudio-pm.gcfg
; GitHub Actions for CI/CD
[IdentityFederation "github-actions"]
Issuer = "https://token.actions.githubusercontent.com"
Audience = "https://github.com/your-org"
Subject = "repo:your-org/your-repo:.*"
; The "data-science-team" group mapped to the "github-actions" federated identity
[GroupToScopeMapping "data-science-team"]
Provider = "github-actions"
Scope = "repos:read:*"
; GitLab CI for another team
[IdentityFederation "gitlab-ci"]
Issuer = "https://gitlab.company.com"
Audience = "packagemanager"
Subject = "repo:your-org/your-repo:.*"
Scope = "repos:read:repo-1"
; The "data-science-team" group mapped to the "gitlab-ci" federated identity
[GroupToScopeMapping "data-science-team"]
Provider = "gitlab-ci"
Scope = "repos:read:*"
Scope = "sources:write:*"
; External partner system
[IdentityFederation "partner-system"]
Issuer = "https://auth.partner.com"
Audience = "partner-audience"
Subject = "^partner-service-.*"
Scope = "repos:read:*"
; Internal service accounts
[IdentityFederation "internal-services"]
Issuer = "https://auth.company.com"
Audience = "internal-audience"
Subject = "^internal-service-.*"
AuthorizedParty = "packagemanager-integration"
Scope = "sources:write:*"
Scope = "metadata:admin"
Token Validation Process
When Package Manager receives a token exchange request, it:
- Validates the request format and required parameters
- Parses the subject token as a JWT
- Checks if the token’s issuer matches any configured
IdentityFederation
provider - Validates the token signature against the issuer’s public keys
- Checks token expiration and other standard JWT claims
- Validates provider-specific claims (audience, subject, authorized party) if configured
- Applies scope mappings based on groups, roles, or base scopes
- Issues a Package Manager access token with the appropriate scopes
When Package Manager receives an API request with a Bearer token, it validates the Package Manager token and grants access based on the token’s scopes.
Troubleshooting Identity Federation
Enable Verbose Logging
/etc/rstudio-pm/rstudio-pm.gcfg
[IdentityFederation "your-provider"]
Logging = true
Common Issues
- Token exchange fails: Verify that the issuer URL exactly matches the token’s
iss
claim - Audience mismatch: Ensure the
audience
setting matches what your external system specifies when requesting tokens - Subject pattern: Check that your subject regex pattern correctly matches the expected token subjects
- Token expiration: Verify that tokens haven’t expired before reaching Package Manager
- Claims missing: Ensure your external provider includes the expected group or role claims in tokens
- Invalid grant type: Ensure you’re using the exact grant type
urn:ietf:params:oauth:grant-type:token-exchange
Testing Token Exchange
You can test the token exchange process manually:
# 1. Get a token from your external provider (this varies by provider)
EXTERNAL_TOKEN="your-oidc-token-here"
# 2. Exchange it for a Package Manager token
PPM_TOKEN_RESPONSE=$(curl -sf -X POST \
'https://packagemanager.example.com/__api__/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode "subject_token=$EXTERNAL_TOKEN" \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:id_token')
# 3. Extract the Package Manager token
PPM_TOKEN=$(echo "$PPM_TOKEN_RESPONSE" | jq -r '.access_token')
# 4. Test API access with the Package Manager token
curl -H "Authorization: Bearer $PPM_TOKEN" \
https://packagemanager.example.com/__api__/verify-auth
Security Considerations
- Token exchange validation: Package Manager thoroughly validates external tokens before issuing internal tokens
- Audience validation: Always configure the
Audience
setting to prevent token reuse across different systems - Subject restrictions: Use
Subject
patterns to limit which service accounts or users can access Package Manager - Scope minimization: Grant only the minimum necessary scopes for each identity provider
- Token expiration: External tokens should have short lifetimes; Package Manager tokens inherit reasonable expiration times
- Secure token storage: Never log or expose tokens in plain text
- Logging: Monitor authentication logs for unusual activity
For detailed examples of scope mapping configurations, see the OpenID Connect Scope Mapping Guide.