Skip to content

feat(ci-oidc): admin UI for CI OIDC provider and identity mapping management#390

Draft
luko0610 wants to merge 3 commits into
artifact-keeper:mainfrom
luko0610:feat/ci-oidc-token-exchange
Draft

feat(ci-oidc): admin UI for CI OIDC provider and identity mapping management#390
luko0610 wants to merge 3 commits into
artifact-keeper:mainfrom
luko0610:feat/ci-oidc-token-exchange

Conversation

@luko0610

Copy link
Copy Markdown

What this PR does

Draft / initial implementation. Tested end-to-end for Docker container push from GitHub Actions and GitLab CI. Not yet tested for other artifact formats Artifact Keeper supports (Maven, npm, Helm, PyPI, raw, etc.) — further format support will be implemented after receiving maintainer feedback

Adds the admin interface for managing CI OIDC providers and identity mappings introduced in the backend companion PR (artifact-keeper#1).

Administrators can configure which CI/CD systems (GitLab CI, GitHub Actions, generic OIDC) are trusted to exchange tokens, and define fine-grained claim-filter rules that determine which pipelines get access and what role they receive — all without storing static secrets.

How it works

The new CI Providers tab appears under Settings → SSO alongside the existing OIDC SSO configuration.

Provider management

  • List all configured CI OIDC providers with status (enabled/disabled)
  • Create a provider: name, type (GitLab / GitHub Actions / Generic), issuer URL, audience
  • Edit, enable/disable, and delete providers

Identity mapping management

  • Per-provider list of mappings in priority order
  • Create/edit a mapping: name, priority, claim filters (key → value or array), optional role assignment
  • Enable/disable individual mappings without deleting them

New files

File Purpose
src/types/ci-oidc.ts TypeScript types for CiOidcProvider, CiOidcIdentityMapping, and all request/response shapes
src/lib/api/ci-oidc.ts API client — CRUD + toggle helpers for providers and mappings
src/app/(app)/(admin)/settings/sso/ci/page.tsx Full CI provider management page

Modified files

  • src/app/(app)/(admin)/settings/sso/page.tsx — added CI Providers tab to the SSO settings index

Verified end-to-end

Companion PR

Backend: artifact-keeper#1

Pipeline usage

The token exchange is a single curl / wget call. To avoid copying it into every project, it should be centralized once and called from each pipeline.

The exchange call (what's happening under the hood)

# CI JWT is passed in the Authorization header — never in the body or env vars
# that might appear in logs.
RESPONSE=$(curl -sf -X POST \
  "$AK_URL/api/v1/auth/ci/token" \
  -H "Authorization: Bearer $CI_JWT" \
  -H "Content-Type: application/json" \
  -d "{\"provider_id\": \"$AK_PROVIDER_ID\"}")

ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r '.access_token')
CI_USERNAME=$(echo "$RESPONSE" | jq -r '.username')

echo "$ACCESS_TOKEN" | docker login "$AK_REGISTRY" \
  --username "$CI_USERNAME" --password-stdin

GitHub Actions — reusable workflow (define once)

Create .github/workflows/ak-login.yml in a central org repo (e.g. your-org/ci-templates):

# .github/workflows/ak-login.yml
on:
  workflow_call:
    inputs:
      ak_url:      { required: true,  type: string }
      provider_id: { required: true,  type: string }
      registry:    { required: true,  type: string }
    outputs:
      ci_username:  { value: "${{ jobs.login.outputs.ci_username }}" }
      access_token: { value: "${{ jobs.login.outputs.access_token }}" }

permissions:
  id-token: write
  contents: read

jobs:
  login:
    runs-on: ubuntu-latest
    outputs:
      ci_username:  ${{ steps.exchange.outputs.ci_username }}
      access_token: ${{ steps.exchange.outputs.access_token }}
    steps:
      - name: Exchange OIDC token
        id: exchange
        run: |
          JWT=$(curl -sf \
            -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=artifact-keeper" \
            | jq -r '.value')

          RESP=$(curl -sf -X POST "${{ inputs.ak_url }}/api/v1/auth/ci/token" \
            -H "Authorization: Bearer $JWT" \
            -H "Content-Type: application/json" \
            -d "{\"provider_id\": \"${{ inputs.provider_id }}\"}")

          echo "::add-mask::$(echo "$RESP" | jq -r '.access_token')"
          echo "access_token=$(echo "$RESP" | jq -r '.access_token')" >> "$GITHUB_OUTPUT"
          echo "ci_username=$(echo "$RESP" | jq -r '.username')"      >> "$GITHUB_OUTPUT"

Calling it from any project (zero curl duplication):

jobs:
  push-image:
    uses: your-org/ci-templates/.github/workflows/ak-login.yml@main
    with:
      ak_url:      https://your-artifact-keeper-instance
      provider_id: <provider-uuid-from-AK-admin>
      registry:    your-artifact-keeper-instance

  publish:
    needs: push-image
    runs-on: ubuntu-latest
    steps:
      - name: Docker login
        run: |
          echo "${{ needs.push-image.outputs.access_token }}" | \
            docker login "$AK_REGISTRY" \
              --username "${{ needs.push-image.outputs.ci_username }}" \
              --password-stdin
      - run: docker build -t "$AK_REGISTRY/$AK_REPO/my-image:${{ github.run_number }}" .
      - run: docker push "$AK_REGISTRY/$AK_REPO/my-image:${{ github.run_number }}"

GitLab CI — include template (define once)

Create a file in a central project (e.g. your-group/ci-templates), say ak-login.yml:

# ak-login.yml  (hosted in your-group/ci-templates)
.ak_login:
  id_tokens:
    AK_OIDC_TOKEN:
      aud: artifact-keeper
  before_script:
    - |
      RESP=$(wget -qO- \
        --post-data="{\"provider_id\":\"$AK_PROVIDER_ID\"}" \
        --header="Content-Type: application/json" \
        --header="Authorization: Bearer $AK_OIDC_TOKEN" \
        "$AK_URL/api/v1/auth/ci/token")

      export ACCESS_TOKEN=$(echo "$RESP" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
      export CI_AK_USERNAME=$(echo "$RESP" | grep -o '"username":"[^"]*"' | cut -d'"' -f4)

      echo "$ACCESS_TOKEN" | docker login "$AK_REGISTRY" \
        --username "$CI_AK_USERNAME" --password-stdin

Calling it from any project (zero curl duplication):

include:
  - project: your-group/ci-templates
    file: ak-login.yml

variables:
  AK_URL:         https://your-artifact-keeper-instance
  AK_PROVIDER_ID: <provider-uuid-from-AK-admin>
  AK_REGISTRY:    your-artifact-keeper-instance
  AK_REPO:        my-repo

push-image:
  stage: publish
  image: docker:27
  services: [docker:27-dind]
  extends: .ak_login      # pulls in id_tokens + before_script
  script:
    - docker build -t "$AK_REGISTRY/$AK_REPO/my-image:$CI_PIPELINE_IID" .
    - docker push "$AK_REGISTRY/$AK_REPO/my-image:$CI_PIPELINE_IID"

Note: GitLab CI uses wget (available in docker:27) because curl and jq are not included in that image. GitHub Actions ubuntu-latest has both natively.

@luko0610 luko0610 force-pushed the feat/ci-oidc-token-exchange branch 3 times, most recently from a3b9ea7 to f5ba4b7 Compare May 10, 2026 14:33
@luko0610 luko0610 force-pushed the feat/ci-oidc-token-exchange branch from 5720ed8 to 56944eb Compare May 10, 2026 20:47
luko0610 added 2 commits May 13, 2026 23:01
…agement

Adds a dedicated CI OIDC management interface under
Settings → SSO → CI Providers, allowing administrators to configure
keyless CI/CD authentication without storing static secrets.

Types (src/types/ci-oidc.ts):
- CiOidcProvider, CiOidcIdentityMapping, and their request/response
  types matching the backend API schema

API client (src/lib/api/ci-oidc.ts):
- CRUD helpers for providers and identity mappings
- Toggle enable/disable for both providers and individual mappings

Pages:
- src/app/(app)/(admin)/settings/sso/ci/page.tsx
  Full management page: provider list, create/edit/delete providers,
  nested identity mapping management with claim-filter editing,
  priority ordering, and role assignment
- src/app/(app)/(admin)/settings/sso/page.tsx
  Extended SSO settings index with CI Providers tab alongside
  existing OIDC SSO configuration
@luko0610 luko0610 force-pushed the feat/ci-oidc-token-exchange branch from 56944eb to 0df19c0 Compare May 13, 2026 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants