Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: 2.1

workflows:
ci:
jobs:
- test:
context:
- sigstore-python

jobs:
test:
docker:
- image: cimg/python:3.10.4
steps:
- checkout
- run:
name: Test signing with ambient credentials
command: |
python -m pip install -U pip
python -m pip install -e .
python -m sigstore sign README.md
20 changes: 18 additions & 2 deletions sigstore/_internal/oidc/ambient.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def detect_credential() -> Optional[str]:
Raises `AmbientCredentialError` if any detector fails internally (i.e.
detects a credential, but cannot retrieve it).
"""
detectors: List[Callable[..., Optional[str]]] = [detect_github]
detectors: List[Callable[..., Optional[str]]] = [detect_github, detect_circleci]
for detector in detectors:
credential = detector()
if credential is not None:
Expand Down Expand Up @@ -76,7 +76,7 @@ def detect_github() -> Optional[str]:
req_url = os.getenv("ACTIONS_ID_TOKEN_REQUEST_URL")
if not req_token or not req_url:
raise AmbientCredentialError(
"GitHub: missing or insufficient OIDC token permissions?"
"GitHub: missing or insufficient OIDC token permissions"
)

resp = requests.get(
Expand All @@ -96,3 +96,19 @@ def detect_github() -> Optional[str]:
return _GitHubTokenPayload(**body).value
except Exception as e:
raise AmbientCredentialError("GitHub: malformed or incomplete JSON") from e


def detect_circleci() -> Optional[str]:
logger.debug("CircleCI: looking for OIDC credentials")

if not os.getenv("CIRCLECI"):
logger.debug("CircleCI: environment doesn't look right; giving up")
return None

token = os.getenv("CIRCLE_OIDC_TOKEN")
if not token:
raise AmbientCredentialError(
"CircleCI: missing or insufficient OIDC token permissions"
)

return token
27 changes: 23 additions & 4 deletions test/internal/oidc/test_ambient.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@


def test_detect_credential_none(monkeypatch):
detect_github = pretend.call_recorder(lambda: None)
monkeypatch.setattr(ambient, "detect_github", detect_github)
detect_noop = pretend.call_recorder(lambda: None)
for detector in ["detect_github", "detect_circleci"]:
monkeypatch.setattr(ambient, detector, detect_noop)
assert ambient.detect_credential() is None


Expand All @@ -32,7 +33,7 @@ def test_detect_credential(monkeypatch):
assert ambient.detect_credential() == "fakejwt"


def test_detect_github_bad_env(monkeypatch):
def test_detect_github_wrong_env(monkeypatch):
# We might actually be running in a CI, so explicitly remove this.
monkeypatch.delenv("GITHUB_ACTIONS", raising=False)

Expand All @@ -56,7 +57,7 @@ def test_detect_github_bad_permissions(monkeypatch):

with pytest.raises(
ambient.AmbientCredentialError,
match="GitHub: missing or insufficient OIDC token permissions?",
match="GitHub: missing or insufficient OIDC token permissions",
):
ambient.detect_github()
assert logger.debug.calls == [
Expand Down Expand Up @@ -136,3 +137,21 @@ def test_detect_github(monkeypatch):
)
]
assert resp.json.calls == [pretend.call()]


def test_detect_circleci_wrong_env(monkeypatch):
# We might actually be running in a CI, so explicitly remove this.
monkeypatch.delenv("CIRCLECI", raising=False)

assert ambient.detect_circleci() is None


def test_detect_circleci_bad_permissions(monkeypatch):
monkeypatch.setenv("CIRCLECI", "true")
monkeypatch.delenv("CIRCLE_OIDC_TOKEN", raising=False)

with pytest.raises(
ambient.AmbientCredentialError,
match="CircleCI: missing or insufficient OIDC token permissions",
):
ambient.detect_circleci()