feat: Add OIDC authentication with automatic CI/CD platform detection#267
feat: Add OIDC authentication with automatic CI/CD platform detection#267cloudsmith-iduffy wants to merge 1 commit intomasterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an OIDC-based authentication fallback to the Cloudsmith CLI (with CI/CD platform auto-detection) plus a new cloudsmith print-token helper command, aiming to support seamless auth in common pipeline environments.
Changes:
- Introduces OIDC detectors (GitHub Actions, GitLab CI, CircleCI, Azure DevOps, Bitbucket Pipelines, AWS, Generic) and an exchange flow to obtain a Cloudsmith token.
- Adds token caching (keyring-first with disk fallback) and keyring support for OIDC token storage.
- Adds
cloudsmith print-tokenand updateswhoami --verbose/ docs to surface OIDC and token-export guidance.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| setup.py | Adds extras_require for optional AWS dependencies. |
| cloudsmith_cli/core/tests/test_credentials.py | Comprehensive tests for provider chain, detectors, exchange, and caching. |
| cloudsmith_cli/core/keyring.py | Adds keyring storage helpers for OIDC token payloads. |
| cloudsmith_cli/core/credentials/init.py | Introduces credential context/result abstractions and provider chain. |
| cloudsmith_cli/core/credentials/providers.py | Adds env/config/keyring/OIDC credential providers. |
| cloudsmith_cli/core/credentials/oidc/init.py | OIDC package module docs. |
| cloudsmith_cli/core/credentials/oidc/cache.py | Implements keyring-first cache with disk fallback and expiry handling. |
| cloudsmith_cli/core/credentials/oidc/exchange.py | Implements /openid/{org}/ exchange with retries/backoff. |
| cloudsmith_cli/core/credentials/oidc/detectors/init.py | Detector registry + priority-ordered detection. |
| cloudsmith_cli/core/credentials/oidc/detectors/base.py | Base detector API + audience helper. |
| cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py | GitHub Actions token retrieval via runtime endpoint. |
| cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py | GitLab JWT env-var detection/selection. |
| cloudsmith_cli/core/credentials/oidc/detectors/circleci.py | CircleCI JWT env-var detection/selection. |
| cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py | Azure DevOps token retrieval via request URI. |
| cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py | Bitbucket token env-var detector. |
| cloudsmith_cli/core/credentials/oidc/detectors/aws.py | AWS identity-based token retrieval via STS (optional boto3). |
| cloudsmith_cli/core/credentials/oidc/detectors/generic.py | Generic/Jenkins env-var fallback detector. |
| cloudsmith_cli/core/api/init.py | Hooks OIDC as last-resort when no SSO/API key present. |
| cloudsmith_cli/cli/commands/whoami.py | Adds OIDC source reporting and print-token hint in verbose output. |
| cloudsmith_cli/cli/commands/print_token.py | New print-token command to output active token. |
| cloudsmith_cli/cli/commands/login.py | Adds a hint about using print-token after login. |
| cloudsmith_cli/cli/commands/init.py | Registers print-token command module import. |
| README.md | Documents optional extras and AWS OIDC installation guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """Bitbucket Pipelines OIDC detector. | ||
|
|
||
| Reads OIDC token from the ``BITBUCKET_STEP_OIDC_TOKEN`` environment variable |
There was a problem hiding this comment.
New source files in this repo appear to include a copyright header comment near the top (e.g., cloudsmith_cli/cli/commands/logout.py:1). Consider adding # Copyright 2026 Cloudsmith Ltd above the module docstring here to match that convention.
| """OIDC token cache. | ||
|
|
||
| Caches Cloudsmith API tokens obtained via OIDC exchange to avoid unnecessary |
There was a problem hiding this comment.
New source files in this repo appear to include a copyright header comment near the top (e.g., cloudsmith_cli/cli/commands/logout.py:1). Consider adding # Copyright 2026 Cloudsmith Ltd above the module docstring here to match that convention.
| """Credential Provider Chain for Cloudsmith CLI. | ||
| Implements an AWS SDK-style credential resolution chain that evaluates |
There was a problem hiding this comment.
New source files in this repo appear to include a copyright header comment near the top (e.g., cloudsmith_cli/cli/commands/logout.py:1). Consider adding # Copyright 2026 Cloudsmith Ltd above the module docstring here to match that convention.
| """AWS OIDC detector. | ||
|
|
||
| Uses boto3 to auto-discover AWS credentials (via any mechanism: env vars, |
There was a problem hiding this comment.
New source files in this repo appear to include a copyright header comment near the top (e.g., cloudsmith_cli/cli/commands/logout.py:1). Consider adding # Copyright 2026 Cloudsmith Ltd above the module docstring here to match that convention.
| """Azure DevOps OIDC detector. | ||
|
|
||
| Fetches OIDC token via the ``SYSTEM_OIDCREQUESTURI`` HTTP endpoint using |
There was a problem hiding this comment.
New source files in this repo appear to include a copyright header comment near the top (e.g., cloudsmith_cli/cli/commands/logout.py:1). Consider adding # Copyright 2026 Cloudsmith Ltd above the module docstring here to match that convention.
| """CircleCI OIDC detector. | ||
|
|
||
| Reads OIDC token from the ``CIRCLE_OIDC_TOKEN_V2`` or ``CIRCLE_OIDC_TOKEN`` |
There was a problem hiding this comment.
New source files in this repo appear to include a copyright header comment near the top (e.g., cloudsmith_cli/cli/commands/logout.py:1). Consider adding # Copyright 2026 Cloudsmith Ltd above the module docstring here to match that convention.
| """GitHub Actions OIDC detector. | ||
|
|
||
| Fetches OIDC token via the Actions runtime HTTP endpoint. |
There was a problem hiding this comment.
New source files in this repo appear to include a copyright header comment near the top (e.g., cloudsmith_cli/cli/commands/logout.py:1). Consider adding # Copyright 2026 Cloudsmith Ltd above the module docstring here to match that convention.
| """CLI/Commands - Print the active authentication token.""" | ||
|
|
||
| import click |
There was a problem hiding this comment.
New source files in this repo appear to include a copyright header comment near the top (e.g., cloudsmith_cli/cli/commands/logout.py:1). Consider adding # Copyright 2026 Cloudsmith Ltd above the module docstring here to match that convention.
| # Detect CI/CD environment and get vendor JWT | ||
| detector = detect_environment(debug=context.debug) | ||
| if detector is None: | ||
| if context.debug: | ||
| logger.debug("OidcProvider: No CI/CD environment detected, skipping") | ||
| return None | ||
|
|
||
| try: | ||
| vendor_token = detector.get_token() | ||
| except Exception: # pylint: disable=broad-exception-caught | ||
| # Detector token retrieval can fail in various ways | ||
| logger.debug( | ||
| "OidcProvider: Failed to retrieve OIDC token from %s", | ||
| detector.name, | ||
| exc_info=True, | ||
| ) | ||
| return None | ||
|
|
||
| if not vendor_token: | ||
| logger.debug( | ||
| "OidcProvider: %s detector returned empty token", detector.name | ||
| ) | ||
| return None | ||
|
|
||
| # Check cache before doing a full exchange | ||
| from .oidc.cache import get_cached_token, store_cached_token | ||
|
|
||
| cached = get_cached_token(context.api_host, org, service_slug) | ||
| if cached: | ||
| logger.debug("OidcProvider: Using cached OIDC token") | ||
| return CredentialResult( | ||
| api_key=cached, | ||
| source_name="oidc", | ||
| source_detail=f"OIDC via {detector.name} [cached] (org: {org}, service: {service_slug})", | ||
| ) |
There was a problem hiding this comment.
OidcProvider.resolve() calls detector.get_token() before checking the cached Cloudsmith token. This means even a cache hit still triggers platform-specific token retrieval (e.g., extra HTTP call in GitHub/Azure) and can fail even though a valid cached token exists. Consider checking get_cached_token(...) immediately after environment detection (before get_token()), and only fetching the vendor JWT when the cache is empty/expired.
| def _try_oidc_credential(config): | ||
| """Attempt OIDC auto-discovery as a last-resort credential provider. | ||
| Only activates when CLOUDSMITH_ORG and CLOUDSMITH_SERVICE_SLUG are set. | ||
| """ | ||
| from ..credentials import CredentialContext | ||
| from ..credentials.providers import OidcProvider | ||
|
|
||
| context = CredentialContext( | ||
| api_host=config.host, | ||
| debug=config.debug, | ||
| ) | ||
|
|
||
| provider = OidcProvider() | ||
| result = provider.resolve(context) | ||
|
|
||
| if result is not None: | ||
| config.api_key["X-Api-Key"] = result.api_key | ||
|
|
||
| if config.debug: | ||
| click.echo(f"OIDC credential resolved: {result.source_detail}") | ||
| elif config.debug: | ||
| logger.debug("OIDC auto-discovery did not resolve credentials") |
There was a problem hiding this comment.
OIDC auto-discovery currently doesn’t appear to respect the CLI’s API networking configuration (proxy, SSL verify, custom headers/user-agent). _try_oidc_credential() only passes api_host into OidcProvider, and exchange_oidc_token() uses requests.post(...) with default settings, so users relying on CLOUDSMITH_API_PROXY / CLOUDSMITH_WITHOUT_API_SSL_VERIFY may see OIDC fail even though normal API calls work. Consider threading proxy/verify (or a configured requests.Session) through CredentialContext into the detector/exchange code.
There was a problem hiding this comment.
Good AI. Let me see what can be done here.
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@cloudsmith-iduffy I've opened a new pull request, #268, to work on those changes. Once the pull request is ready, I'll request review from you. |
…pport This PR adds credential helper infrastructure for Docker and pip package managers, enabling automatic authentication to Cloudsmith registries without manual login commands or embedding credentials in URLs. This builds upon #267 (OIDC authentication) and extends the credential provider chain to Docker and pip workflows. ## Features ### Docker Credential Helper - Command: `cloudsmith credential-helper docker` - Binary: `docker-credential-cloudsmith` (for Docker CLI integration) - Supports standard domains: `docker.cloudsmith.io`, `*.docker.cloudsmith.io` - Supports custom vanity domains via API auto-discovery ### Pip Keyring Backend - Auto-discovered by pip/twine via `keyring.backends` entry point - Supports standard domains: `python.cloudsmith.io`, `dl.cloudsmith.io`, `*.cloudsmith.sh` - Supports custom vanity domains via API auto-discovery - Priority: 9.9 (runs before system keychains) ### Custom Domain Support - Set `CLOUDSMITH_ORG=my-org` to enable custom domain discovery - Fetches domains from `GET /orgs/{org}/custom-domains/` API endpoint - Caches results in `~/.cloudsmith/cache/custom_domains/` for 1 hour - Automatic, no manual configuration needed ### Shared Architecture - Both helpers use the same credential provider chain - Order: Environment Variable → Config File → Keyring → OIDC - Consistent authentication behavior across all package managers - Extensible design for future helpers (npm, cargo, maven, etc.) ## Type of Change - [x] New feature - [x] Documentation update ## Backward Compatibility - ✅ Fully backward compatible - ✅ No breaking changes - ✅ Existing authentication methods unchanged - ✅ Optional features - enable by configuration ## Testing ### Manual Testing - Docker Environment setup: ```bash $ env | grep CLOUDSMITH_ CLOUDSMITH_ORG=iduffy-demo CLOUDSMITH_SERVICE_SLUG=default-v9ty $ stat ~/.cloudsmith/config.ini stat: cannot stat '/Users/iduffy/.cloudsmith/config.ini': No such file or directory $ cloudsmith whoami --verbose Retrieving your authentication status from the API ... OK User: default (slug: default-v9ty) Authentication Method: OIDC Auto-Discovery Source: OIDC auto-discovery: AWS (org: iduffy-demo) Token Slug: 6FmYSZVQrEho Created: 2025-06-07T19:43:47.840466Z SSO Status: Not configured Keyring: Enabled (no tokens stored) ``` Before configuration: ```bash $ cat ~/.docker/config.json cat: /Users/iduffy/.docker/config.json: No such file or directory $ docker pull docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest Error response from daemon: Head "https://docker.cloudsmith.io/v2/iduffy-demo/default/library/ubuntu/manifests/latest": unauthorized ``` After configuration: ```bash $ cat ~/.docker/config.json { "credHelpers": { "docker.cloudsmith.io": "cloudsmith" } } $ docker pull docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest latest: Pulling from iduffy-demo/default/library/ubuntu cc43ec4c1381: Pull complete Digest: sha256:9cbed754112939e914291337b5e554b07ad7c392491dba6daf25eef1332a22e8 Status: Downloaded newer image for docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest ``` ✅ **Success:** Docker authenticated using AWS OIDC credentials via credential helper ### Manual Testing - Pip Environment setup: ```bash $ pip config list :env:.default-timeout='100' :env:.disable-pip-version-check='1' $ python -c 'import keyring; print(keyring.backend.get_all_keyring())' [<keyring.backends.fail.Keyring object at 0x1033a4460>, <cloudsmith_cli.credential_helpers.pip.CloudsmithKeyringBackend object at 0x1033a4c80>, <keyring.backends.chainer.ChainerBackend object at 0x1033a5360>, <keyring.backends.macOS.Keyring object at 0x1033a5720>] ``` Test installation (no credentials in URL): ```bash $ pip install --no-cache-dir --index-url=https://dl.cloudsmith.io/basic/iduffy-demo/default/python/simple/ cloudsmith-python-native Looking in indexes: https://dl.cloudsmith.io/basic/iduffy-demo/default/python/simple/ Collecting cloudsmith-python-native Downloading https://dl.cloudsmith.io/basic/iduffy-demo/default/python/cloudsmith_python_native-1.0.1050047-py2.py3-none-any.whl (2.2 kB) Requirement already satisfied: toml in /Users/iduffy/projects/cloudsmith-cli/.venv/lib/python3.10/site-packages (from cloudsmith-python-native) (0.10.2) Installing collected packages: cloudsmith-python-native Successfully installed cloudsmith-python-native-1.0.1050047 ``` ✅ **Success:** Pip authenticated using AWS OIDC credentials via keyring backend ### Automated Testing - ✅ Pylint: 10.00/10 (perfect score) - ✅ Tests: 199 passed, 39 skipped - ✅ No new test failures introduced ## Documentation Added comprehensive documentation: - `CUSTOM_DOMAINS.md` - Complete guide for custom domain configuration - `PIP_KEYRING_POC.md` - Proof of concept guide for pip keyring backend - `PIP_KEYRING_SUCCESS.md` - Success report with usage examples - `pip_keyring_poc.sh` - Automated testing script ## Additional Notes ### Architecture Validation The credential helper architecture was validated by implementing two different package manager protocols (Docker binary stdin/stdout vs Python keyring API) to ensure the design is flexible and extensible. ### Custom Domain Support Custom domains are discovered automatically when `CLOUDSMITH_ORG` is set: 1. First request fetches domains from API 2. Results cached for 1 hour in `~/.cloudsmith/cache/custom_domains/` 3. Subsequent requests use cache (no repeated API calls) 4. Works seamlessly with OIDC in CI/CD environments ### Future Extensions The architecture is designed to support additional credential helpers: - npm (`.npmrc` credential helper) - cargo (Cargo credential helper) - maven (Maven settings credential helper) - gradle (Gradle properties credential helper) - nuget (NuGet credential helper) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pport This PR adds credential helper infrastructure for Docker and pip package managers, enabling automatic authentication to Cloudsmith registries without manual login commands or embedding credentials in URLs. This builds upon #267 (OIDC authentication) and extends the credential provider chain to Docker and pip workflows. - Command: `cloudsmith credential-helper docker` - Binary: `docker-credential-cloudsmith` (for Docker CLI integration) - Supports standard domains: `docker.cloudsmith.io`, `*.docker.cloudsmith.io` - Supports custom vanity domains via API auto-discovery - Auto-discovered by pip/twine via `keyring.backends` entry point - Supports standard domains: `python.cloudsmith.io`, `dl.cloudsmith.io` - Supports custom vanity domains via API auto-discovery - Set `CLOUDSMITH_ORG=my-org` to enable custom domain discovery - Fetches domains from `GET /orgs/{org}/custom-domains/` API endpoint - Caches results in `~/.cloudsmith/cache/custom_domains/` for 1 hour - Automatic, no manual configuration needed - Both helpers use the same credential provider chain - Order: Environment Variable → Config File → Keyring → OIDC - Consistent authentication behavior across all package managers - Extensible design for future helpers (npm, cargo, maven, etc.) - [x] New feature - [x] Documentation update - ✅ Fully backward compatible - ✅ No breaking changes - ✅ Existing authentication methods unchanged - ✅ Optional features - enable by configuration Environment setup: ```bash $ env | grep CLOUDSMITH_ CLOUDSMITH_ORG=iduffy-demo CLOUDSMITH_SERVICE_SLUG=default-v9ty $ stat ~/.cloudsmith/config.ini stat: cannot stat '/Users/iduffy/.cloudsmith/config.ini': No such file or directory $ cloudsmith whoami --verbose Retrieving your authentication status from the API ... OK User: default (slug: default-v9ty) Authentication Method: OIDC Auto-Discovery Source: OIDC auto-discovery: AWS (org: iduffy-demo) Token Slug: 6FmYSZVQrEho Created: 2025-06-07T19:43:47.840466Z SSO Status: Not configured Keyring: Enabled (no tokens stored) ``` Before configuration: ```bash $ cat ~/.docker/config.json cat: /Users/iduffy/.docker/config.json: No such file or directory $ docker pull docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest Error response from daemon: Head "https://docker.cloudsmith.io/v2/iduffy-demo/default/library/ubuntu/manifests/latest": unauthorized ``` After configuration: ```bash $ cat ~/.docker/config.json { "credHelpers": { "docker.cloudsmith.io": "cloudsmith" } } $ docker pull docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest latest: Pulling from iduffy-demo/default/library/ubuntu cc43ec4c1381: Pull complete Digest: sha256:9cbed754112939e914291337b5e554b07ad7c392491dba6daf25eef1332a22e8 Status: Downloaded newer image for docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest ``` Environment setup: ```bash $ pip config list :env:.default-timeout='100' :env:.disable-pip-version-check='1' $ python -c 'import keyring; print(keyring.backend.get_all_keyring())' [<keyring.backends.fail.Keyring object at 0x1033a4460>, <cloudsmith_cli.credential_helpers.pip.CloudsmithKeyringBackend object at 0x1033a4c80>, <keyring.backends.chainer.ChainerBackend object at 0x1033a5360>, <keyring.backends.macOS.Keyring object at 0x1033a5720>] ``` Test installation (no credentials in URL): ```bash $ pip install --no-cache-dir --index-url=https://dl.cloudsmith.io/basic/iduffy-demo/default/python/simple/ cloudsmith-python-native Looking in indexes: https://dl.cloudsmith.io/basic/iduffy-demo/default/python/simple/ Collecting cloudsmith-python-native Downloading https://dl.cloudsmith.io/basic/iduffy-demo/default/python/cloudsmith_python_native-1.0.1050047-py2.py3-none-any.whl (2.2 kB) Requirement already satisfied: toml in /Users/iduffy/projects/cloudsmith-cli/.venv/lib/python3.10/site-packages (from cloudsmith-python-native) (0.10.2) Installing collected packages: cloudsmith-python-native Successfully installed cloudsmith-python-native-1.0.1050047 ``` Custom domains are discovered automatically when `CLOUDSMITH_ORG` is set: 1. First request fetches domains from API 2. Results cached for 1 hour in `~/.cloudsmith/cache/custom_domains/` 3. Subsequent requests use cache (no repeated API calls)
…pport This PR adds credential helper infrastructure for Docker and pip package managers, enabling automatic authentication to Cloudsmith registries without manual login commands or embedding credentials in URLs. This builds upon #267 (OIDC authentication) and extends the credential provider chain to Docker and pip workflows. - Command: `cloudsmith credential-helper docker` - Binary: `docker-credential-cloudsmith` (for Docker CLI integration) - Supports standard domains: `docker.cloudsmith.io`, `*.docker.cloudsmith.io` - Supports custom vanity domains via API auto-discovery - Auto-discovered by pip/twine via `keyring.backends` entry point - Supports standard domains: `python.cloudsmith.io`, `dl.cloudsmith.io` - Supports custom vanity domains via API auto-discovery - Set `CLOUDSMITH_ORG=my-org` to enable custom domain discovery - Fetches domains from `GET /orgs/{org}/custom-domains/` API endpoint - Caches results in `~/.cloudsmith/cache/custom_domains/` for 1 hour - Automatic, no manual configuration needed - Both helpers use the same credential provider chain - Order: Environment Variable → Config File → Keyring → OIDC - Consistent authentication behavior across all package managers - Extensible design for future helpers (npm, cargo, maven, etc.) - [x] New feature - [x] Documentation update - ✅ Fully backward compatible - ✅ No breaking changes - ✅ Existing authentication methods unchanged - ✅ Optional features - enable by configuration Environment setup: ```bash $ env | grep CLOUDSMITH_ CLOUDSMITH_ORG=iduffy-demo CLOUDSMITH_SERVICE_SLUG=default-v9ty $ stat ~/.cloudsmith/config.ini stat: cannot stat '/Users/iduffy/.cloudsmith/config.ini': No such file or directory $ cloudsmith whoami --verbose Retrieving your authentication status from the API ... OK User: default (slug: default-v9ty) Authentication Method: OIDC Auto-Discovery Source: OIDC auto-discovery: AWS (org: iduffy-demo) Token Slug: 6FmYSZVQrEho Created: 2025-06-07T19:43:47.840466Z SSO Status: Not configured Keyring: Enabled (no tokens stored) ``` Before configuration: ```bash $ cat ~/.docker/config.json cat: /Users/iduffy/.docker/config.json: No such file or directory $ docker pull docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest Error response from daemon: Head "https://docker.cloudsmith.io/v2/iduffy-demo/default/library/ubuntu/manifests/latest": unauthorized ``` After configuration: ```bash $ cat ~/.docker/config.json { "credHelpers": { "docker.cloudsmith.io": "cloudsmith" } } $ docker pull docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest latest: Pulling from iduffy-demo/default/library/ubuntu cc43ec4c1381: Pull complete Digest: sha256:9cbed754112939e914291337b5e554b07ad7c392491dba6daf25eef1332a22e8 Status: Downloaded newer image for docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest ``` Environment setup: ```bash $ pip config list :env:.default-timeout='100' :env:.disable-pip-version-check='1' $ python -c 'import keyring; print(keyring.backend.get_all_keyring())' [<keyring.backends.fail.Keyring object at 0x1033a4460>, <cloudsmith_cli.credential_helpers.pip.CloudsmithKeyringBackend object at 0x1033a4c80>, <keyring.backends.chainer.ChainerBackend object at 0x1033a5360>, <keyring.backends.macOS.Keyring object at 0x1033a5720>] ``` Test installation (no credentials in URL): ```bash $ pip install --no-cache-dir --index-url=https://dl.cloudsmith.io/basic/iduffy-demo/default/python/simple/ cloudsmith-python-native Looking in indexes: https://dl.cloudsmith.io/basic/iduffy-demo/default/python/simple/ Collecting cloudsmith-python-native Downloading https://dl.cloudsmith.io/basic/iduffy-demo/default/python/cloudsmith_python_native-1.0.1050047-py2.py3-none-any.whl (2.2 kB) Requirement already satisfied: toml in /Users/iduffy/projects/cloudsmith-cli/.venv/lib/python3.10/site-packages (from cloudsmith-python-native) (0.10.2) Installing collected packages: cloudsmith-python-native Successfully installed cloudsmith-python-native-1.0.1050047 ``` Custom domains are discovered automatically when `CLOUDSMITH_ORG` is set: 1. First request fetches domains from API 2. Results cached for 1 hour in `~/.cloudsmith/cache/custom_domains/` 3. Subsequent requests use cache (no repeated API calls)
…pport This PR adds credential helper infrastructure for Docker and pip package managers, enabling automatic authentication to Cloudsmith registries without manual login commands or embedding credentials in URLs. This builds upon #267 (OIDC authentication) and extends the credential provider chain to Docker and pip workflows. - Command: `cloudsmith credential-helper docker` - Binary: `docker-credential-cloudsmith` (for Docker CLI integration) - Supports standard domains: `docker.cloudsmith.io`, `*.docker.cloudsmith.io` - Supports custom vanity domains via API auto-discovery - Auto-discovered by pip/twine via `keyring.backends` entry point - Supports standard domains: `python.cloudsmith.io`, `dl.cloudsmith.io` - Supports custom vanity domains via API auto-discovery - Set `CLOUDSMITH_ORG=my-org` to enable custom domain discovery - Fetches domains from `GET /orgs/{org}/custom-domains/` API endpoint - Caches results in `~/.cloudsmith/cache/custom_domains/` for 1 hour - Automatic, no manual configuration needed - Both helpers use the same credential provider chain - Order: Environment Variable → Config File → Keyring → OIDC - Consistent authentication behavior across all package managers - Extensible design for future helpers (npm, cargo, maven, etc.) - [x] New feature - [x] Documentation update - ✅ Fully backward compatible - ✅ No breaking changes - ✅ Existing authentication methods unchanged - ✅ Optional features - enable by configuration Environment setup: ```bash $ env | grep CLOUDSMITH_ CLOUDSMITH_ORG=iduffy-demo CLOUDSMITH_SERVICE_SLUG=default-v9ty $ stat ~/.cloudsmith/config.ini stat: cannot stat '/Users/iduffy/.cloudsmith/config.ini': No such file or directory $ cloudsmith whoami --verbose Retrieving your authentication status from the API ... OK User: default (slug: default-v9ty) Authentication Method: OIDC Auto-Discovery Source: OIDC auto-discovery: AWS (org: iduffy-demo) Token Slug: 6FmYSZVQrEho Created: 2025-06-07T19:43:47.840466Z SSO Status: Not configured Keyring: Enabled (no tokens stored) ``` Before configuration: ```bash $ cat ~/.docker/config.json cat: /Users/iduffy/.docker/config.json: No such file or directory $ docker pull docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest Error response from daemon: Head "https://docker.cloudsmith.io/v2/iduffy-demo/default/library/ubuntu/manifests/latest": unauthorized ``` After configuration: ```bash $ cat ~/.docker/config.json { "credHelpers": { "docker.cloudsmith.io": "cloudsmith" } } $ docker pull docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest latest: Pulling from iduffy-demo/default/library/ubuntu cc43ec4c1381: Pull complete Digest: sha256:9cbed754112939e914291337b5e554b07ad7c392491dba6daf25eef1332a22e8 Status: Downloaded newer image for docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest docker.cloudsmith.io/iduffy-demo/default/library/ubuntu:latest ``` Environment setup: ```bash $ pip config list :env:.default-timeout='100' :env:.disable-pip-version-check='1' $ python -c 'import keyring; print(keyring.backend.get_all_keyring())' [<keyring.backends.fail.Keyring object at 0x1033a4460>, <cloudsmith_cli.credential_helpers.pip.CloudsmithKeyringBackend object at 0x1033a4c80>, <keyring.backends.chainer.ChainerBackend object at 0x1033a5360>, <keyring.backends.macOS.Keyring object at 0x1033a5720>] ``` Test installation (no credentials in URL): ```bash $ pip install --no-cache-dir --index-url=https://dl.cloudsmith.io/basic/iduffy-demo/default/python/simple/ cloudsmith-python-native Looking in indexes: https://dl.cloudsmith.io/basic/iduffy-demo/default/python/simple/ Collecting cloudsmith-python-native Downloading https://dl.cloudsmith.io/basic/iduffy-demo/default/python/cloudsmith_python_native-1.0.1050047-py2.py3-none-any.whl (2.2 kB) Requirement already satisfied: toml in /Users/iduffy/projects/cloudsmith-cli/.venv/lib/python3.10/site-packages (from cloudsmith-python-native) (0.10.2) Installing collected packages: cloudsmith-python-native Successfully installed cloudsmith-python-native-1.0.1050047 ``` Custom domains are discovered automatically when `CLOUDSMITH_ORG` is set: 1. First request fetches domains from API 2. Results cached for 1 hour in `~/.cloudsmith/cache/custom_domains/` 3. Subsequent requests use cache (no repeated API calls)
Implements AWS-style credential provider chain with automatic OIDC token
discovery for 7 major CI/CD platforms (GitHub Actions, GitLab CI, CircleCI,
Azure DevOps, Bitbucket Pipelines, AWS, and Jenkins/Generic).
Type of Change:
- New feature
Key Features:
- Credential provider chain with priority: CLI flag > env var > config file > keyring > OIDC
- Auto-detects CI/CD environment and exchanges vendor JWT for Cloudsmith token
- Keyring-first token caching with disk fallback
- New `cloudsmith print-token` command to export tokens for curl, docker, etc.
- Platform-specific OIDC detection shown in `whoami --verbose` output
Platform Support:
- GitHub Actions: Fetches token via ACTIONS_ID_TOKEN_REQUEST_URL
- GitLab CI: Reads CI_JOB_JWT_V2 or CI_JOB_JWT
- CircleCI: Reads CIRCLE_OIDC_TOKEN_V2 or CIRCLE_OIDC_TOKEN
- Azure DevOps: Fetches token via SYSTEM_OIDCREQUESTURI
- Bitbucket Pipelines: Reads BITBUCKET_STEP_OIDC_TOKEN
- AWS (ECS/EKS/EC2/Lambda): Uses boto3 credential chain + STS GetWebIdentityToken
- Generic/Jenkins: Reads CLOUDSMITH_OIDC_TOKEN (requires credentials-binding plugin)
Example Usage (AWS):
```bash
$ env | grep CLOUDSMITH_
CLOUDSMITH_ORG=iduffy-demo
CLOUDSMITH_SERVICE_SLUG=default-v9ty
$ stat ~/.cloudsmith/config.ini
stat: cannot stat '/Users/iduffy/.cloudsmith/config.ini': No such file or directory
$ aws sts get-caller-identity
{
"UserId": "AROA47EXAMPLE:ian@ianduffy.ie",
"Account": "893EXAMPLE",
"Arn": "arn:aws:sts::893EXAMPLE:assumed-role/AWSReservedSSO_NOPE_..."
}
$ cloudsmith whoami --verbose
Retrieving your authentication status from the API ... OK
User: default (slug: default-v9ty)
Authentication Method: OIDC Auto-Discovery
Source: OIDC auto-discovery: AWS (org: iduffy-demo)
Token Slug: sACcPOv3Iro8
Created: 2025-06-07T19:43:47.840466Z
💡 Export this token: cloudsmith print-token
$ cloudsmith list repos
$ cloudsmith print-token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
$ curl -H "X-Api-Key: $(cloudsmith print-token)" https://api.cloudsmith.io/v1/user/self/
$ docker login docker.cloudsmith.io -u token -p $(cloudsmith print-token)
```
Configuration:
Requires two environment variables:
- CLOUDSMITH_ORG: Organization slug
- CLOUDSMITH_SERVICE_SLUG: Service account slug
Optional:
- CLOUDSMITH_OIDC_AUDIENCE: Override default audience (default: "cloudsmith")
- CLOUDSMITH_NO_KEYRING=1: Skip keyring, use disk cache only
Installation:
- Base install: pip install cloudsmith-cli
- With AWS support: pip install cloudsmith-cli[aws]
- All features: pip install cloudsmith-cli[all]
Security:
- Tokens cached with 60-second expiry margin for auto-refresh
- Cache files ideally use keyring but if they go to disk created with 0o600 permissions
- Exponential backoff with jitter for retry resilience
Backwards Compatibility:
- No breaking changes to existing authentication methods
- OIDC is opt-in via environment variables
- Existing API key/SSO authentication unaffected
e3b1e4a to
df70f37
Compare
Implements AWS-style credential provider chain with automatic OIDC token discovery for 7 major CI/CD platforms (GitHub Actions, GitLab CI, CircleCI, Azure DevOps, Bitbucket Pipelines, AWS, and Jenkins/Generic).
Type of Change:
Key Features:
cloudsmith print-tokencommand to export tokens for curl, docker, etc.whoami --verboseoutputPlatform Support:
Example Usage (AWS):
Configuration:
Requires two environment variables:
Optional:
Installation:
Security:
Backwards Compatibility: