Skip to content

Commit 68ab21d

Browse files
feat: add OIDC detector enable/order controls
Add two env-var controls for OIDC detector selection: - CLOUDSMITH_OIDC_<DETECTOR>_DISABLED skips a specific detector (only the literal "true", case-insensitive, disables). - CLOUDSMITH_OIDC_DETECTOR_ORDER overrides which detectors are considered and the order they are tried in (comma-separated slugs; unlisted/unknown slugs are skipped). When both are set the order list defines the candidate set and sequence, then the disable flags are applied on top, so a disabled detector is always skipped. Each detector gains a stable `slug` attribute and a public `registered_detectors()` accessor is added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 792d9d6 commit 68ab21d

12 files changed

Lines changed: 214 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1616
- Added GitHub Actions to OIDC credential auto-discovery. When running in GitHub Actions (with `id-token: write` permission), the CLI fetches an OIDC token from the Actions runtime endpoint and exchanges it for a Cloudsmith access token. Works out of the box with no extra dependencies.
1717
- Added a generic fallback to OIDC credential auto-discovery. When no dedicated environment is detected, the CLI reads an OIDC token from the `CLOUDSMITH_OIDC_TOKEN` environment variable (useful for Jenkins or any custom CI/CD) and exchanges it for a Cloudsmith access token. Works out of the box with no extra dependencies.
1818
- Added GitLab CI to OIDC credential auto-discovery. When running in GitLab CI/CD, the CLI reads the OIDC token from the `CLOUDSMITH_OIDC_TOKEN` environment variable (configured via `id_tokens` in `.gitlab-ci.yml`) and exchanges it for a Cloudsmith access token. Works out of the box with no extra dependencies.
19+
- Added controls for OIDC detector selection. Set `CLOUDSMITH_OIDC_<DETECTOR>_DISABLED=true` to skip a specific detector (only the literal `true` disables), or `CLOUDSMITH_OIDC_DETECTOR_ORDER` to a comma-separated list of detector slugs to override which detectors are considered and the order they are tried in. When both are set, disable flags take precedence over the order list. Detector slugs: `aws`, `azure_devops`, `bitbucket`, `circleci`, `generic`, `github`, `gitlab`.
1920

2021
## [1.18.0] - 2026-06-09
2122

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,15 @@ See the [Cloudsmith GitLab CI/CD integration guide](https://docs.cloudsmith.com/
211211

212212
As a fallback for environments without a dedicated detector (for example Jenkins with the [credentials binding plugin](https://plugins.jenkins.io/credentials-binding/), or any custom CI/CD system), set the `CLOUDSMITH_OIDC_TOKEN` environment variable to an OIDC JWT and the CLI will exchange it for a Cloudsmith access token. This detector runs last, so a dedicated environment is always preferred when present. See the [Cloudsmith Jenkins OIDC guide](https://docs.cloudsmith.com/authentication/setup-jenkins-to-authenticate-to-cloudsmith-using-oidc).
213213

214+
#### Controlling OIDC Detector Selection
215+
216+
By default the CLI tries each detector in a fixed priority order and uses the first that matches. Two environment variables let you override this when a detector matches an environment you don't want it to (for example, the AWS detector matching ambient instance credentials):
217+
218+
- **Disable a detector** — set `CLOUDSMITH_OIDC_<DETECTOR>_DISABLED=true` to skip it entirely. Only the literal value `true` (case-insensitive) disables; anything else leaves the detector enabled. For example, `CLOUDSMITH_OIDC_AWS_DISABLED=true` skips the AWS detector so an explicitly-set `CLOUDSMITH_OIDC_TOKEN` is picked up by the generic detector instead.
219+
- **Reorder evaluation** — set `CLOUDSMITH_OIDC_DETECTOR_ORDER` to a comma-separated list of detector slugs to control both which detectors are considered and the order they are tried in (first match wins). Slugs not listed are skipped; unrecognised slugs are ignored. For example, `CLOUDSMITH_OIDC_DETECTOR_ORDER=generic,aws` tries the generic detector first and considers only those two.
220+
221+
When both are set, the order list defines the candidate set and sequence, then the `*_DISABLED` flags are applied on top — so a disabled detector is always skipped even if it appears in the order list. Detector slugs are: `aws`, `azure_devops`, `bitbucket`, `circleci`, `generic`, `github`, `gitlab`.
222+
214223
## Configuration
215224

216225
There are two configuration files used by the CLI:

cloudsmith_cli/core/credentials/oidc/detectors/__init__.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import logging
6+
import os
67
from typing import TYPE_CHECKING
78

89
from .aws import AWSDetector
@@ -29,12 +30,70 @@
2930
GenericDetector,
3031
]
3132

33+
DETECTOR_ORDER_ENV_VAR = "CLOUDSMITH_OIDC_DETECTOR_ORDER"
34+
35+
36+
def registered_detectors() -> list[type[EnvironmentDetector]]:
37+
"""Return the registered OIDC detectors in their default priority order."""
38+
return list(_DETECTORS)
39+
40+
41+
def _disable_env_var(slug: str) -> str:
42+
return f"CLOUDSMITH_OIDC_{slug.upper()}_DISABLED"
43+
44+
45+
def _is_detector_disabled(slug: str) -> bool:
46+
"""Whether a detector is disabled via its CLOUDSMITH_OIDC_<SLUG>_DISABLED var.
47+
48+
Only the literal string ``true`` (case-insensitive) disables; anything
49+
else leaves the detector enabled.
50+
"""
51+
return os.environ.get(_disable_env_var(slug), "false").strip().lower() == "true"
52+
53+
54+
def _ordered_detectors() -> list[type[EnvironmentDetector]]:
55+
"""Detectors in evaluation order, honouring CLOUDSMITH_OIDC_DETECTOR_ORDER.
56+
57+
When the order var is unset/empty the default registration order is used.
58+
Otherwise only the listed slugs are considered, in the listed order;
59+
unknown slugs are logged and skipped.
60+
"""
61+
raw_order = os.environ.get(DETECTOR_ORDER_ENV_VAR, "").strip()
62+
if not raw_order:
63+
return list(_DETECTORS)
64+
65+
detectors_by_slug = {detector_cls.slug: detector_cls for detector_cls in _DETECTORS}
66+
ordered: list[type[EnvironmentDetector]] = []
67+
for token in raw_order.split(","):
68+
slug = token.strip().lower()
69+
if not slug:
70+
continue
71+
detector_cls = detectors_by_slug.get(slug)
72+
if detector_cls is None:
73+
logger.debug(
74+
"Ignoring unknown OIDC detector slug in %s: %s",
75+
DETECTOR_ORDER_ENV_VAR,
76+
slug,
77+
)
78+
continue
79+
ordered.append(detector_cls)
80+
return ordered
81+
82+
83+
def _enabled_detectors() -> list[type[EnvironmentDetector]]:
84+
"""Ordered detectors with disabled ones removed (disable always wins)."""
85+
return [
86+
detector_cls
87+
for detector_cls in _ordered_detectors()
88+
if not _is_detector_disabled(detector_cls.slug)
89+
]
90+
3291

3392
def detect_environment(
3493
context: CredentialContext,
3594
) -> EnvironmentDetector | None:
3695
"""Try each detector in order, returning the first that matches."""
37-
for detector_cls in _DETECTORS:
96+
for detector_cls in _enabled_detectors():
3897
detector = detector_cls(context=context)
3998
try:
4099
if detector.detect():

cloudsmith_cli/core/credentials/oidc/detectors/aws.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class AWSDetector(EnvironmentDetector):
2424
"""Detects AWS environments and obtains a JWT via STS GetWebIdentityToken."""
2525

2626
name = "AWS"
27+
slug = "aws"
2728

2829
def __init__(self, context):
2930
super().__init__(context)

cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class AzureDevOpsDetector(EnvironmentDetector):
3030
"""Detects Azure DevOps and fetches an OIDC token via HTTP POST."""
3131

3232
name = "Azure DevOps"
33+
slug = "azure_devops"
3334

3435
def detect(self) -> bool:
3536
return bool(os.environ.get("SYSTEM_OIDCREQUESTURI")) and bool(

cloudsmith_cli/core/credentials/oidc/detectors/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class EnvironmentDetector:
1212
"""Base class for OIDC environment detectors."""
1313

1414
name: str = "base"
15+
slug: str = "base"
1516

1617
def __init__(self, context: CredentialContext):
1718
self.context = context

cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class BitbucketPipelinesDetector(EnvironmentDetector):
2020
"""Detects Bitbucket Pipelines and reads its OIDC token from environment."""
2121

2222
name = "Bitbucket Pipelines"
23+
slug = "bitbucket"
2324

2425
def detect(self) -> bool:
2526
return bool(os.environ.get("BITBUCKET_STEP_OIDC_TOKEN"))

cloudsmith_cli/core/credentials/oidc/detectors/circleci.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class CircleCIDetector(EnvironmentDetector):
2020
"""Detects CircleCI and reads OIDC token from environment variable."""
2121

2222
name = "CircleCI"
23+
slug = "circleci"
2324

2425
def detect(self) -> bool:
2526
return os.environ.get("CIRCLECI") == "true" and bool(

cloudsmith_cli/core/credentials/oidc/detectors/generic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class GenericDetector(EnvironmentDetector):
2727
"""
2828

2929
name = "Generic"
30+
slug = "generic"
3031

3132
def detect(self) -> bool:
3233
return bool((os.environ.get(TOKEN_ENV_VAR) or "").strip())

cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class GitHubActionsDetector(EnvironmentDetector):
2525
"""Detects GitHub Actions and fetches an OIDC token via HTTP request."""
2626

2727
name = "GitHub Actions"
28+
slug = "github"
2829

2930
def detect(self) -> bool:
3031
return (

0 commit comments

Comments
 (0)