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.
If Vault auth fails with connection errors, verify VAULT_ADDR is set at group level (group 9) in GitLab.
Related Documentation¶
- Overview — Port architecture and repository types
- Docker Registry — Docker push/pull authentication examples
- RPM and Raw — RPM upload authentication
- PyPI and npm — Python package authentication