Skip to content

Service Accounts and CI/CD Authentication

All CI/CD pipelines authenticate to Nexus through HashiCorp Vault using JWT tokens. Credentials are never hardcoded.

Authentication Flow

GitLab pipelines use OIDC-based Vault JWT auth to retrieve Nexus credentials at runtime:

GitLab Pipeline
  → id_tokens: VAULT_ID_TOKEN (aud: https://gitlab.shared.cwiq.io)
  → POST ${VAULT_ADDR}/v1/auth/jwt/login  (role: nexus-ci)
  → Receive VAULT_TOKEN
  → GET ${VAULT_ADDR}/v1/secret/data/nexus/{svc-account}
  → Extract NEXUS_USER + NEXUS_PASSWORD
  → docker login / curl / twine / npm login

Vault JWT configuration: - Audience: https://gitlab.shared.cwiq.io - Role: nexus-ci - Token type: JWT (GitLab id_tokens block)

Use role: nexus-ci — not nexus-read-policy

The JWT role name is nexus-ci. An earlier naming mismatch (nexus-read-policy) caused auth failures across multiple pipelines. Always use nexus-ci as the role when authenticating to Vault for Nexus credentials.

Service Accounts

Per-Application Accounts (CI/CD Push)

These accounts are used by CI pipelines to push artifacts to Nexus.

Account Application Artifact Types Vault Path
svc-orchestrator Platform services Docker + RPM + Raw + PyPI secret/nexus/svc-orchestrator
svc-executor Executor CLI RPM + Raw secret/nexus/svc-executor
svc-datastore Datastore Docker secret/nexus/svc-datastore
svc-bench Bench RPM secret/nexus/svc-bench
svc-fsdb FSDB/CWIQFS RPM secret/nexus/svc-fsdb

System Accounts

Account Purpose Vault Path
svc-promoter Artifact promotion dev→uat→prod (skopeo/curl) secret/nexus/svc-promoter
svc-deployer Pull-only — K8s imagePullSecrets, EC2 deployments secret/nexus/svc-deployer
svc-admin Administrative automation secret/nexus/svc-admin

Which Account to Use

Operation Account
Platform service Docker push svc-orchestrator
cwiq-common PyPI publish svc-orchestrator
Executor RPM push svc-executor
Executor raw binary push svc-executor
K8s imagePullSecret svc-deployer
Promote dev→uat artifact svc-promoter

CI/CD Configuration

Alpine/Slim Images (curl-based Vault auth)

Most CI jobs use this pattern to fetch credentials:

.nexus_auth: &nexus_auth
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://gitlab.shared.cwiq.io
  before_script:
    - |
      VAULT_TOKEN=$(curl -sf -X POST "${VAULT_ADDR}/v1/auth/jwt/login" \
        --data "{\"jwt\":\"${VAULT_ID_TOKEN}\",\"role\":\"nexus-ci\"}" \
        | jq -r .auth.client_token)
      export NEXUS_USER=$(curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
        "${VAULT_ADDR}/v1/secret/data/nexus/svc-orchestrator" \
        | jq -r .data.data.username)
      export NEXUS_PASSWORD=$(curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
        "${VAULT_ADDR}/v1/secret/data/nexus/svc-orchestrator" \
        | jq -r .data.data.password)
      docker login "${NEXUS_REGISTRY}" -u "${NEXUS_USER}" -p "${NEXUS_PASSWORD}"
      docker login "${NEXUS_REGISTRY_PULL}" -u "${NEXUS_USER}" -p "${NEXUS_PASSWORD}"

Kaniko Builds (wget-based Vault auth)

Kaniko does not include curl — use wget instead:

build_image:
  image:
    name: nexus.shared.cwiq.io:8444/gcr.io/kaniko-project/executor:v1.23.2-debug
    entrypoint: [""]
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://gitlab.shared.cwiq.io
  script:
    - |
      VAULT_TOKEN=$(wget -qO- --post-data="{\"jwt\":\"${VAULT_ID_TOKEN}\",\"role\":\"nexus-ci\"}" \
        --header="Content-Type: application/json" \
        "${VAULT_ADDR}/v1/auth/jwt/login" | jq -r .auth.client_token)
      NEXUS_USER=$(wget -qO- --header="X-Vault-Token: ${VAULT_TOKEN}" \
        "${VAULT_ADDR}/v1/secret/data/nexus/svc-orchestrator" | jq -r .data.data.username)
      NEXUS_PASSWORD=$(wget -qO- --header="X-Vault-Token: ${VAULT_TOKEN}" \
        "${VAULT_ADDR}/v1/secret/data/nexus/svc-orchestrator" | jq -r .data.data.password)
      # Create Kaniko auth config
      AUTH=$(printf "%s:%s" "${NEXUS_USER}" "${NEXUS_PASSWORD}" | base64 -w 0)
      mkdir -p /kaniko/.docker
      printf '{"auths":{"%s":{"auth":"%s"},"%s":{"auth":"%s"}}}' \
        "${NEXUS_REGISTRY_PUSH}" "${AUTH}" \
        "${NEXUS_REGISTRY_PULL}" "${AUTH}" \
        > /kaniko/.docker/config.json

Alpine BusyBox wget incompatibility

Alpine's wget (BusyBox) does not support --post-data with JSON. If your CI image uses Alpine, install curl via apk add curl before the Vault auth step, or use a non-Alpine base image. This has caused auth failures in multiple pipelines.

RPM Upload (executor pipeline)

upload_rpm:
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://gitlab.shared.cwiq.io
  script:
    - |
      VAULT_TOKEN=$(curl -sf -X POST "${VAULT_ADDR}/v1/auth/jwt/login" \
        --data "{\"jwt\":\"${VAULT_ID_TOKEN}\",\"role\":\"nexus-ci\"}" \
        | jq -r .auth.client_token)
      NEXUS_USER=$(curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
        "${VAULT_ADDR}/v1/secret/data/nexus/svc-executor" | jq -r .data.data.username)
      NEXUS_PASSWORD=$(curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
        "${VAULT_ADDR}/v1/secret/data/nexus/svc-executor" | jq -r .data.data.password)
      curl --upload-file "dist/${RPM_FILE}" \
        -u "${NEXUS_USER}:${NEXUS_PASSWORD}" \
        "${NEXUS_RPM_REPO}/orchestrator/executor/${RPM_FILE}"

PyPI Publish (cwiq-common pipeline)

publish_pypi:
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://gitlab.shared.cwiq.io
  script:
    - |
      VAULT_TOKEN=$(curl -sf -X POST "${VAULT_ADDR}/v1/auth/jwt/login" \
        --data "{\"jwt\":\"${VAULT_ID_TOKEN}\",\"role\":\"nexus-ci\"}" \
        | jq -r .auth.client_token)
      NEXUS_USER=$(curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
        "${VAULT_ADDR}/v1/secret/data/nexus/svc-orchestrator" | jq -r .data.data.username)
      NEXUS_PASSWORD=$(curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
        "${VAULT_ADDR}/v1/secret/data/nexus/svc-orchestrator" | jq -r .data.data.password)
      twine upload \
        --repository-url "${NEXUS_PYPI_PUSH_URL}" \
        --username "${NEXUS_USER}" \
        --password "${NEXUS_PASSWORD}" \
        dist/*

Authentication by Context

Context Auth Method Credential Source
GitLab CI (Alpine/slim) curl to Vault JWT endpoint id_tokens.VAULT_ID_TOKEN
GitLab CI (Kaniko) wget to Vault JWT endpoint id_tokens.VAULT_ID_TOKEN
Docker push/pull docker login with NEXUS_USER/NEXUS_PASSWORD Vault secret/nexus/svc-*
RPM upload curl --upload-file -u basic auth Vault secret/nexus/svc-*
PyPI publish twine --username --password Vault secret/nexus/svc-*
Kubernetes pull imagePullSecrets (docker-registry type) Vault secret/nexus/svc-deployer

Kubernetes imagePullSecret

For K8s deployments pulling from Nexus:

# Create docker-registry secret for K8s
kubectl create secret docker-registry nexus-pull \
  --docker-server=nexus.shared.cwiq.io:8444 \
  --docker-username="${NEXUS_USER}" \
  --docker-password="${NEXUS_PASSWORD}" \
  --namespace=orchestrator
# In pod spec
spec:
  imagePullSecrets:
    - name: nexus-pull
  containers:
    - image: nexus.shared.cwiq.io:8444/orchestrator/server:latest

The svc-deployer account has nx-pull-all role — pull access across all repositories.

VAULT_ADDR Variable

VAULT_ADDR must be set as a GitLab group-level CI variable (not project-level). It is used by all pipelines across all projects under the orchestrator group.

https://vault.shared.cwiq.io

If Vault auth fails with connection errors, verify VAULT_ADDR is set at group level (group 9) in GitLab.