Skip to content
Merged
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
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,45 @@ jobs:
- uses: actions/checkout@v7
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- name: Build native container for runtime smoke tests
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
load: true
push: false
tags: workbench:smoke
cache-from: type=gha
- name: Verify non-root Azure runtime
run: >-
docker run --rm --entrypoint sh workbench:smoke -c
'test "$(id -u)" = 1001 &&
test "$AZURE_CONFIG_DIR" = /home/nextjs/.azure &&
test "$(stat -c %a "$AZURE_CONFIG_DIR")" = 700 &&
test -w "$AZURE_CONFIG_DIR" &&
az version --output json --only-show-errors >/dev/null &&
node -e "require(\"sharp\")" &&
wget --version >/dev/null'
- name: Build arm64 container for runtime smoke tests
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/arm64
load: true
push: false
tags: workbench:smoke-arm64
cache-from: type=gha
- name: Verify non-root Azure runtime on arm64
run: >-
docker run --rm --platform linux/arm64 --entrypoint sh
workbench:smoke-arm64 -c
'test "$(id -u)" = 1001 &&
test "$AZURE_CONFIG_DIR" = /home/nextjs/.azure &&
test "$(stat -c %a "$AZURE_CONFIG_DIR")" = 700 &&
test -w "$AZURE_CONFIG_DIR" &&
az version --output json --only-show-errors >/dev/null &&
node -e "require(\"sharp\")" &&
wget --version >/dev/null'
- uses: docker/build-push-action@v7
with:
context: .
Expand Down
31 changes: 30 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,36 @@
All notable changes to Workbench are documented here. Versions follow semantic
versioning.

## 0.1.0 - 2026-07-18
## 1.1.0 - 2026-07-19

### Added

- UI-managed Microsoft device-code sign-in for a personal Azure account, with
no user-created Entra application registration or terminal step.
- Azure Key Vault as an optional source for bearer tokens, Basic passwords, API
keys, OAuth client secrets, OAuth passwords, and OAuth refresh tokens.
- Exact or latest Key Vault secret references, sanitized connection and
failure states, reference testing, and server-side just-in-time resolution.
- Persistent, isolated Azure CLI authentication state in the standard Docker
Compose installation.

### Changed

- The standard production and development containers now include Azure CLI
2.88.0 and use Debian Bookworm while remaining non-root and multi-platform.
- Export manifests now report the package version instead of a stale hard-coded
value.

### Security

- Key Vault tokens and resolved values remain outside browser responses,
PostgreSQL, execution history, exports, backups, and application logs.
- Vault references accept only HTTPS Azure Key Vault hosts, preventing access
tokens from being forwarded to arbitrary servers.
- Azure CLI processes use fixed argument arrays without a shell, bounded output,
one active login, cancellation, expiry, and sanitized errors.

## 1.0.0 - 2026-07-18

Initial public release.

Expand Down
47 changes: 35 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
# syntax=docker/dockerfile:1

FROM --platform=$BUILDPLATFORM node:24-alpine AS build-base
RUN apk add --no-cache libc6-compat
FROM --platform=$BUILDPLATFORM node:24-bookworm-slim AS build-base
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1

FROM node:24-bookworm-slim AS azure-cli-base
ARG AZURE_CLI_VERSION=2.88.0
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
ENV AZURE_CONFIG_DIR=/home/nextjs/.azure

RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg wget \
&& mkdir -p /etc/apt/keyrings \
&& curl -sLS https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor -o /etc/apt/keyrings/microsoft.gpg \
&& chmod go+r /etc/apt/keyrings/microsoft.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ bookworm main" \
> /etc/apt/sources.list.d/azure-cli.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends "azure-cli=${AZURE_CLI_VERSION}-1~bookworm" \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system --gid 1001 nodejs \
&& useradd --system --uid 1001 --gid nodejs --home-dir /home/nextjs --create-home --shell /usr/sbin/nologin nextjs \
&& mkdir -p "$AZURE_CONFIG_DIR" \
&& chown -R nextjs:nodejs /home/nextjs \
&& chmod 700 "$AZURE_CONFIG_DIR"

FROM build-base AS dependencies
COPY package.json package-lock.json ./
RUN npm ci

FROM dependencies AS development
FROM azure-cli-base AS development
ENV NODE_ENV=development
COPY . .
COPY --from=dependencies --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --chown=nextjs:nodejs . .
RUN mkdir -p /app/.next \
&& chown nextjs:nodejs /app/.next
USER nextjs
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0"]

Expand All @@ -28,21 +54,18 @@ RUN case "$TARGETARCH" in \
amd64) target_npm_cpu=x64 ;; \
*) target_npm_cpu="$TARGETARCH" ;; \
esac \
&& npm ci --omit=dev --ignore-scripts --os=linux --cpu="$target_npm_cpu" --libc=musl \
&& test -d "node_modules/@img/sharp-linuxmusl-$target_npm_cpu" \
&& test -d "node_modules/@img/sharp-libvips-linuxmusl-$target_npm_cpu" \
&& npm ci --omit=dev --ignore-scripts --os=linux --cpu="$target_npm_cpu" --libc=glibc \
&& test -d "node_modules/@img/sharp-linux-$target_npm_cpu" \
&& test -d "node_modules/@img/sharp-libvips-linux-$target_npm_cpu" \
&& npm cache clean --force

FROM node:24-alpine AS runner
FROM azure-cli-base AS runner
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
ENV WORKBENCH_BACKUP_DIR=/backups

RUN apk add --no-cache libc6-compat \
&& addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs \
&& mkdir -p /backups \
RUN mkdir -p /backups \
&& chown nextjs:nodejs /backups \
&& chmod 700 /backups

Expand Down
54 changes: 51 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,34 @@ authentication, connected request flows, and durable project organisation. It
runs as a self-hosted Next.js application with PostgreSQL, so collections,
credentials, execution history, imports, and backups remain under your control.

> [!IMPORTANT]
> **Azure Key Vault is built directly into Workbench authentication in v1.1.**
> Connect your personal Microsoft account through a guided, `az login`-style
> flow in the UI, then use Key Vault secrets in authentication profiles. There
> is no terminal login, no user-created Entra application registration, and no
> resolved vault value stored in Workbench.

## Use Azure Key Vault without leaving the app

Workbench can source bearer tokens, Basic-auth passwords, API keys, OAuth client
secrets, OAuth passwords, and OAuth refresh tokens from Azure Key Vault. Pick
**Azure Key Vault** on the credential field, enter the vault URL and secret name,
and optionally pin an exact version. Leaving the version blank follows the
latest secret so normal rotation does not require editing the profile.

The Microsoft device-code flow, connection status, reference test, and
disconnect controls all live on the Authentication screen. Azure CLI is already
included in the standard amd64 and arm64 Docker images, and its isolated session
survives normal Compose container recreation.

![Azure Key Vault authentication in Workbench](docs/images/phase-12-azure-key-vault.png)

[Read the Azure Key Vault setup and security details](docs/authentication.md#connect-azure).

It is designed for developers who need more than isolated HTTP requests:

- resolve authentication secrets from Azure Key Vault only when a request needs
them;
- obtain a token from one saved request and inject it into another;
- publish response values for later requests and workflows;
- explain exactly which environment or variable supplied a value;
Expand Down Expand Up @@ -47,6 +73,26 @@ tokens are cached server-side and refreshed before expiry.

![OAuth client credentials profile](docs/images/phase-10-oauth-client-credentials.png)

Credential fields can be stored in Workbench or resolved from Azure Key Vault.
The Authentication screen connects a personal Microsoft account using a guided
device-code flow entirely in the UI—there is no terminal command and no
user-created Microsoft Entra application registration. Workbench supports Key
Vault references for:

| Authentication profile | Key Vault-backed fields |
| ------------------------ | ------------------------------- |
| Bearer token | Token |
| Basic authentication | Password |
| API key | Key value |
| OAuth client credentials | Client secret |
| OAuth password | Client secret and password |
| OAuth refresh token | Client secret and refresh token |

Each reference identifies a vault URL, secret name, and optional exact version.
Omitting the version follows the latest secret, enabling rotation without
editing the profile. Values are resolved only on the server immediately before
use and never enter Workbench records, history, exports, or backups.

A request-derived profile connects normal saved requests into an authentication
flow:

Expand Down Expand Up @@ -186,8 +232,9 @@ Stop the stack without deleting data:
docker compose down
```

The `workbench_postgres_data` and `workbench_backups` volumes preserve the
database and logical backups across restarts. Compose defaults use development
The `workbench_postgres_data`, `workbench_backups`, and
`workbench_azure_cli` volumes preserve the database, logical backups, and
optional Azure sign-in respectively. Compose defaults use development
credentials; set unique `POSTGRES_*` values before exposing the database beyond
the local Docker network.

Expand All @@ -200,7 +247,7 @@ Every verified merge to `master` publishes a non-root, multi-platform image for
ghcr.io/josh-uk/workbench
```

Images receive `latest` and full-commit-SHA tags. Version tags such as `v0.1.0`
Images receive `latest` and full-commit-SHA tags. Version tags such as `v1.1.0`
also publish semantic-version tags, an SBOM, build provenance, and a GitHub
release. GitHub Container Registry visibility is managed separately from the
public repository.
Expand Down Expand Up @@ -235,6 +282,7 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
| `APP_PORT` | Host port mapped to the application | `3000` |
| `WORKBENCH_BACKUP_DIR` | Server-only logical backup directory | `/backups` |
| `WORKBENCH_BACKUP_PASSWORD` | Password for encrypted automatic backups (12+) | Empty |
| `AZURE_CONFIG_DIR` | Isolated Azure CLI authentication state | `/home/nextjs/.azure` |

Never use a `NEXT_PUBLIC_` variable for a secret.

Expand Down
7 changes: 3 additions & 4 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ triage the impact, and coordinate remediation and disclosure.

## Supported versions

Before `v1.0.0`, the latest `0.1.x` release and the latest commit on `master` are
supported. Security fixes are released from current development; older pre-1.0
minor lines do not receive backports. The stable support window will be
documented with the `v1.0.0` release.
The latest stable minor release and the latest commit on `master` are supported.
Security fixes are released from current development; older minor lines do not
normally receive backports.

## Scope

Expand Down
3 changes: 3 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ services:
- .:/app
- workbench_node_modules:/app/node_modules
- workbench_next_cache:/app/.next
- workbench_azure_cli:/home/nextjs/.azure

volumes:
workbench_node_modules:
workbench_next_cache:
workbench_azure_cli:
name: workbench_azure_cli
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ services:
restart: unless-stopped
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-workbench}:${POSTGRES_PASSWORD:-workbench}@database:5432/${POSTGRES_DB:-workbench}
AZURE_CONFIG_DIR: /home/nextjs/.azure
WORKBENCH_BACKUP_DIR: /backups
WORKBENCH_BACKUP_PASSWORD: ${WORKBENCH_BACKUP_PASSWORD:-}
ports:
Expand All @@ -18,6 +19,7 @@ services:
condition: service_healthy
volumes:
- workbench_backups:/backups
- workbench_azure_cli:/home/nextjs/.azure
healthcheck:
test:
[
Expand Down Expand Up @@ -55,3 +57,5 @@ volumes:
name: workbench_postgres_data
workbench_backups:
name: workbench_backups
workbench_azure_cli:
name: workbench_azure_cli
6 changes: 5 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ results to HTTP responses. Database and secret-bearing modules import
## Runtime

The production container starts by applying committed database migrations, then
launches the Next.js standalone server as a non-root user. Compose waits for
launches the Next.js standalone server as a non-root user. The standard image
also contains a pinned Azure CLI. Its optional user-session cache is isolated in
`workbench_azure_cli`; Workbench launches only fixed Azure CLI argument arrays
and resolves Key Vault values through server-only modules. Compose waits for
PostgreSQL health before starting the app. `/api/health` verifies both the server
and database connection.

Expand All @@ -34,6 +37,7 @@ and database connection.
- Saved-request editor and persistence
- Variable resolver
- Authentication and request-output engine
- UI-managed Azure login and Azure Key Vault secret resolution
- SSRF-aware HTTP execution engine
- Modular collection importers
- Workflow and assertion runner
Expand Down
Loading
Loading