# DVE Connectors — Secret Injection Setup

Use this guide to inject DVE secrets into CI/CD pipelines, Kubernetes workloads, and automated scripts **without installing the DVE Agent** on the runner. Connectors are client-side packages in the [PK-DVE-Connectors](https://github.com/phantomkey/PK-DVE-Connectors) repository.

## Section 1 — Overview

**What connectors are.** The `@phantomkey/dve-connector-*` packages are Node.js clients that pull secrets from DVE's **agentless secret vending API** using a **service account** credential. Each connector:

1. Issues a short-lived access token (`POST /api/org/:org_id/service-accounts/:id/token`)
2. Fetches the secret value (`GET /api/org/:org_id/secrets/:secret_id/agentless-value`)
3. Injects the value into the runtime environment (GitHub Actions env, GitLab shell script, Kubernetes volume files, or shell exports)

DVE does **not** push secrets to external platforms — connectors pull over HTTPS.

**When to use them.** CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI), Kubernetes init containers or sidecars, and any automated job that needs machine identity access to secrets.

**What they are NOT.** Connectors do **not** replace the DVE Agent for **human users**. Employees and contractors still need the local DVE Agent for interactive credential access, encryption, and decryption on their endpoints. Connectors are the **machine identity** path only.

**Architecture note.** Agentless vending is an intentional exception to the default agent-required model: the server vends pre-encrypted agentless envelope values for scoped service accounts when the org enables hybrid mode. Human credential decryption still requires the agent and user passphrase. See the <a href="#" data-dve-tab="security" class="dve-guide-tab-link">Security architecture</a> docs for the zero-existence model.

| Package | Use case |
|---------|----------|
| `@phantomkey/dve-connector-github` | GitHub Actions |
| `@phantomkey/dve-connector-gitlab` | GitLab CI |
| `@phantomkey/dve-connector-kubernetes` | Kubernetes init container / sidecar |
| `@phantomkey/dve-connector-cli` | Generic shell / any CI with Node.js |
| `@phantomkey/dve-connector-core` | Shared API client (used by all four) |

---

## Section 2 — Prerequisites

Complete these steps **before** configuring a connector.

### Org settings

1. **Enable hybrid secret vending** — required for agentless fetches.
   - **Admin Dashboard:** Settings → Deployment → enable hybrid secret vending (or equivalent org policy control).
   - **API:** `POST /api/org/:org_id/policy/secret-vending/enable-hybrid` (Super Admin / Security Admin with appropriate capability).

2. Confirm `secret_vending_mode` is `hybrid` (not `agent_only`).

### Service account

1. Provision a **service account** with `vend_mode: agentless` and a narrow `allowed_secret_ids` list.
   - **API:** `POST /api/org/:org_id/service-accounts` (agent-authenticated admin).
2. Issue an API key: `POST /api/org/:org_id/service-accounts/:service_account_id/issue-key`.
3. Store the raw API key in your CI/CD secret store — it is shown once.

### Secrets in DVE

- Each secret must exist in the org vault.
- For agentless vending, the secret must have an **agentless envelope** uploaded (hybrid mode workflow).
- The service account's `allowed_secret_ids` must include every secret UUID you map in the connector.

### Environment variables (all connectors)

```bash
DVE_API_URL=https://your-dve-backend.example.com   # HTTPS required
DVE_ORG_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx    # Org UUID
DVE_SERVICE_ACCOUNT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
DVE_SERVICE_ACCOUNT_KEY=your-service-account-api-key  # Sensitive — use secret store
```

### Secret mapping format

Connectors map DVE secret UUIDs to environment variable names using newline-separated lines:

```text
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx=DATABASE_PASSWORD
yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy=API_KEY
```

---

## Section 3 — GitHub Actions

**What it does.** Fetches DVE secrets and injects them as **masked** environment variables for subsequent workflow steps via `GITHUB_ENV`.

### Quick start

Store credentials as GitHub **secrets** (sensitive) and **variables** (non-sensitive):

| Name | GitHub storage | Sensitive |
|------|----------------|-----------|
| `DVE_API_URL` | Repository variable | No |
| `DVE_ORG_ID` | Repository variable | No |
| `DVE_SERVICE_ACCOUNT_ID` | Repository secret | Yes |
| `DVE_SERVICE_ACCOUNT_KEY` | Repository secret | Yes |

### Workflow example

When the action is published to GitHub Marketplace, reference it by tag. Until then, install from npm in a composite step or use a local action path.

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Inject DVE secrets
        uses: phantomkey/dve-connector-github@v1
        with:
          dve_api_url: ${{ vars.DVE_API_URL }}
          org_id: ${{ vars.DVE_ORG_ID }}
          service_account_id: ${{ secrets.DVE_SERVICE_ACCOUNT_ID }}
          service_account_key: ${{ secrets.DVE_SERVICE_ACCOUNT_KEY }}
          secrets: |
            xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx=DATABASE_PASSWORD
            yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy=API_KEY

      - name: Deploy application
        run: npm run deploy
        env:
          DATABASE_PASSWORD: ${{ env.DATABASE_PASSWORD }}
          API_KEY: ${{ env.API_KEY }}
```

Alternative using npm directly (no marketplace action):

```yaml
      - name: Inject DVE secrets (npm)
        env:
          DVE_API_URL: ${{ vars.DVE_API_URL }}
          DVE_ORG_ID: ${{ vars.DVE_ORG_ID }}
          DVE_SERVICE_ACCOUNT_ID: ${{ secrets.DVE_SERVICE_ACCOUNT_ID }}
          DVE_SERVICE_ACCOUNT_KEY: ${{ secrets.DVE_SERVICE_ACCOUNT_KEY }}
          INPUT_SECRETS: |
            xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx=DATABASE_PASSWORD
        run: |
          npm install -g @phantomkey/dve-connector-github@0.1.0
          node -e "require('@phantomkey/dve-connector-github').runGithubConnector()"
```

**Security:** Never commit `DVE_SERVICE_ACCOUNT_KEY` to the repository. The connector masks values in logs before writing to `GITHUB_ENV`.

**Full parameter reference:** [PK-DVE-Connectors `packages/github/action.yml`](https://github.com/phantomkey/PK-DVE-Connectors/blob/main/packages/github/action.yml)

---

## Section 4 — GitLab CI

**What it does.** Fetches secrets and writes a sourceable shell script (`dve-secrets.sh` by default) with `export VAR='value'` lines.

### CI/CD variables

Configure in GitLab → Settings → CI/CD → Variables:

| Variable | Masked | Protected |
|----------|--------|-----------|
| `DVE_API_URL` | No | Optional |
| `DVE_ORG_ID` | No | Optional |
| `DVE_SERVICE_ACCOUNT_ID` | Yes | Recommended |
| `DVE_SERVICE_ACCOUNT_KEY` | Yes | Recommended |

Set `DVE_SECRET_MAPPINGS` in the job (secret UUIDs are not sensitive; values are fetched at runtime):

```yaml
stages:
  - build

build_with_dve:
  stage: build
  image: node:22-alpine
  variables:
    DVE_API_URL: "https://your-dve-backend.example.com"
    DVE_ORG_ID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    DVE_SERVICE_ACCOUNT_ID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    DVE_SECRET_MAPPINGS: |
      xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx=DATABASE_PASSWORD
      yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy=API_KEY
    DVE_ENV_OUTPUT: "./dve-secrets.sh"
  script:
    - npm install -g @phantomkey/dve-connector-gitlab@0.1.0
    - node -e "require('@phantomkey/dve-connector-gitlab').runGitlabConnector()"
    - chmod 600 ./dve-secrets.sh
    - source ./dve-secrets.sh
    - npm ci && npm test
  secrets:
    DVE_SERVICE_ACCOUNT_KEY:
      vault: production/dve/service-account-key@gitlab
      file: false
```

### Reusable template

Copy the `.dve_fetch_secrets` job from the connector package template:

**Full reference:** [PK-DVE-Connectors `packages/gitlab/src/template.yml`](https://github.com/phantomkey/PK-DVE-Connectors/blob/main/packages/gitlab/src/template.yml)

---

## Section 5 — Kubernetes

**Patterns.**

| Pattern | When to use |
|---------|-------------|
| **Init container** | Fetch secrets once before the app container starts (most common) |
| **Sidecar** | Re-run the connector on an interval for rotation (wrap `runKubernetesConnector()` in a loop; not built into the package) |

Secrets are written as **one file per environment variable name** under `DVE_SECRETS_OUTPUT_DIR` (default `/etc/dve-secrets`), mode `0600`, on a shared `emptyDir` volume (prefer `medium: Memory`).

### Init container example

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-with-dve-secrets
spec:
  initContainers:
    - name: dve-secret-fetcher
      image: node:22-alpine
      command:
        - sh
        - -c
        - |
          npm install -g @phantomkey/dve-connector-kubernetes@0.1.0
          node -e "require('@phantomkey/dve-connector-kubernetes').runKubernetesConnector()"
      env:
        - name: DVE_API_URL
          valueFrom:
            configMapKeyRef:
              name: dve-config
              key: api-url
        - name: DVE_ORG_ID
          valueFrom:
            configMapKeyRef:
              name: dve-config
              key: org-id
        - name: DVE_SERVICE_ACCOUNT_ID
          valueFrom:
            secretKeyRef:
              name: dve-credentials
              key: service-account-id
        - name: DVE_SERVICE_ACCOUNT_KEY
          valueFrom:
            secretKeyRef:
              name: dve-credentials
              key: service-account-key
        - name: DVE_SECRET_MAPPINGS
          value: |
            xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx=DATABASE_PASSWORD
            yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy=API_KEY
        - name: DVE_SECRETS_OUTPUT_DIR
          value: /etc/dve-secrets
      volumeMounts:
        - name: dve-secrets
          mountPath: /etc/dve-secrets
  containers:
    - name: app
      image: your-app:latest
      env:
        - name: DATABASE_PASSWORD_FILE
          value: /etc/dve-secrets/DATABASE_PASSWORD
      volumeMounts:
        - name: dve-secrets
          mountPath: /etc/dve-secrets
          readOnly: true
  volumes:
    - name: dve-secrets
      emptyDir:
        medium: Memory
```

Read secret values from files in your application entrypoint, or map file contents to env vars in your startup script.

**Complete example:** [PK-DVE-Connectors `packages/kubernetes/example-pod.yaml`](https://github.com/phantomkey/PK-DVE-Connectors/blob/main/packages/kubernetes/example-pod.yaml)

---

## Section 6 — Generic CLI / Shell

**What it is.** The `dve-secret` binary (`@phantomkey/dve-connector-cli`) fetches secrets and prints `export VAR='value'` lines to stdout for `eval`, or runs a command with secrets in the environment.

### Installation

```bash
npm install -g @phantomkey/dve-connector-cli
```

Requires Node.js 22+. Set all four `DVE_*` variables before invoking.

### Single secret

```bash
export DVE_API_URL="https://your-dve-backend.example.com"
export DVE_ORG_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export DVE_SERVICE_ACCOUNT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export DVE_SERVICE_ACCOUNT_KEY="your-api-key"

eval "$(dve-secret inject --secret xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --env-var DATABASE_PASSWORD)"
```

### Batch mappings file

Create `secrets.map`:

```text
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx=DATABASE_PASSWORD
yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy=API_KEY
```

```bash
eval "$(dve-secret inject --mappings-file ./secrets.map)"
npm run deploy
```

### Run a command with secrets in scope

```bash
dve-secret inject \
  --secret xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
  --env-var API_KEY \
  --exec node deploy.js
```

Works in Jenkins, CircleCI, Bitbucket Pipelines, or any environment that can run Node.js and set environment variables.

**Full reference:** [PK-DVE-Connectors `packages/cli/README.md`](https://github.com/phantomkey/PK-DVE-Connectors/blob/main/packages/cli/README.md)

---

## Section 7 — Security Notes

- **Service account keys are long-lived credentials.** Store them only in your platform's secret management (GitHub Secrets, GitLab masked variables, Kubernetes Secrets, Vault).
- **Never commit** service account keys or secret values to source control.
- **Scope narrowly.** Create one service account per pipeline or workload with `allowed_secret_ids` limited to the secrets that job needs.
- **Audit trail.** Every connector fetch writes to DVE's secret audit chain:
  - `secret_token_issued` when the access token is created
  - `secret_value_vended` when the agentless value is returned
  Events are attributed to the service account identity.
- **Rotate keys** on the same schedule as the secrets they access. Re-issue via `issue-key` and update CI/CD variables.
- **TLS only.** Connectors reject non-HTTPS `DVE_API_URL` values.
- **No disk persistence by default.** GitHub and GitLab inject into process environment; Kubernetes writes to a memory-backed volume — restrict volume access to the app container.

---

## Section 8 — Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| **401 Unauthorized** | Wrong or missing `DVE_SERVICE_ACCOUNT_ID` / `DVE_SERVICE_ACCOUNT_KEY` | Verify credentials; confirm service account status is `active` in DVE |
| **403 Forbidden** (secret scope) | Service account lacks access to the secret UUID | Update `allowed_secret_ids` for the service account in DVE |
| **403** `agent_only_mode_active` | Org `secret_vending_mode` is not `hybrid` | Enable hybrid vending in Admin Dashboard → Settings → Deployment, or `POST .../policy/secret-vending/enable-hybrid` |
| **404 Secret not found** | Invalid `secret_id` or secret not in this org | Confirm UUID in Admin Dashboard; verify secret exists and is not deprecated |

### Additional checks

- **Missing agentless envelope:** Secret exists but agentless fetch fails — ensure hybrid mode is on and the agentless envelope was uploaded for that secret version.
- **Empty mappings:** GitHub `INPUT_SECRETS`, GitLab/K8s `DVE_SECRET_MAPPINGS`, or CLI mappings file must use `UUID=ENV_VAR` format with valid UUIDs.
- **Connector logs:** All connectors exit code `1` on failure; read stderr in CI job output.

---

## Related documentation

- [Deployment guides index](index.md)
- [Deployment overview](overview.md)
- [Manual agent installation](manual.md) — required for human users
- [PK-DVE-Connectors repository](https://github.com/phantomkey/PK-DVE-Connectors)
- Service account API: `POST /api/org/:org_id/service-accounts`
