Relay — Headless VPN auth for CI/CD and servers

AWS Client VPN requires a browser for SAML authentication. That's fine on a developer laptop — impossible on a CI runner or a remote VM behind NAT.

The problem with SAML in headless environments

AWS Client VPN uses the CRV1 SAML challenge-response protocol. Phase 1 returns a URL that must be opened in a browser — the user logs in via their IdP (Okta, Azure AD, etc.) and the resulting SAMLResponse completes Phase 2 to bring up the tunnel.

🚫

CI/CD runners have no browser

GitHub Actions, GitLab CI, Jenkins, and most cloud runners are headless Ubuntu containers. There is no display, no browser, no way to complete the SAML flow interactively.

🔑

Storing credentials is an anti-pattern

Injecting AWS credentials or SAML tokens as CI secrets couples your pipeline to credentials that expire, rotate, and leak. Every rotation breaks builds.

🔧

Existing workarounds fail at scale

Pre-generated SAML tokens expire in minutes. Spinning up a full desktop VM just for VPN auth wastes money and adds minutes to every build.

How Relay works

The relay is a lightweight broker between the headless agent (CI runner) and the human operator (Android or desktop app). No inbound ports required. No secrets stored anywhere in the pipeline.

open|awsvpn relay — headless SAML auth for CI/CD VPN Integration · GitHub Actions Set up openlawsvpn Checkout Start relay agent $ openlawsvpn-cli \ -relay $RELAY_TOKEN -daemon \ -pidfile /tmp/vpn.pid waiting for app to connect... Check internal service health Disconnect VPN Upload relay agent log TUNNEL UP tun=172.16.77.1 pid=12483 daemon started · zero SAML secrets in CI Relay APIGW · DDB · SQS Lambda WS register phase2 push POST /execute Relay Organisation Token OEJlyJA8•••Nhtvw Save & Refresh Available Agents build-runner-01 standby Connect Delivering… Agent tunnel is up. session f5d1c84c Profiles Connect Relay Settings openlawsvpn Android one tap · zero secrets in CI AWS Client VPN vpn.us-east-1.amazonaws.com your existing infra — nothing changes Phase 1 Phase 2 · tunnel up ✓ CRV1::state_id::SAMLResponse SAML relay — headless CI runner authenticates via human approval on phone. No browser on the server. No credentials in CI env.
1

Agent registers. CI runner starts openlawsvpn-cli -relay <token> -daemon and connects a WebSocket to the relay. No inbound port needed.

2

App authenticates. Developer opens the Android or desktop app, sees the agent listed as standby, taps Connect. The app runs Phase 1 + full SAML browser flow.

3

Relay delivers credentials. App sends the completed SAMLResponse to the relay via HTTPS. Relay pushes it to the waiting agent over the WebSocket.

4

Tunnel up. Agent executes Phase 2, VPN is established. CI build continues. App shows the agent as connected and can disconnect remotely at any time.

See it in action

A real screencast: a GitHub Actions workflow starts the relay agent, the Android app approves the SAML flow, and the tunnel comes up — with zero credentials stored in the pipeline.

What the screencast shows

  1. A GitHub Actions VPN Integration workflow is triggered manually. The runner is a headless Ubuntu container with no display.
  2. The workflow step Start relay agent runs openlawsvpn-cli -relay $RELAY_TOKEN -daemon -pidfile /tmp/vpn.pid. The step blocks, waiting for an operator to approve auth from the app.
  3. On an Android phone, the openlawsvpn app's Relay screen shows the runner listed as standby. The operator taps Connect.
  4. The app runs the Phase 1 SAML browser flow. The AWS-hosted login page opens in Chrome Custom Tab. The user authenticates with their SSO credentials.
  5. The app posts the completed SAMLResponse to the relay backend. The relay pushes it to the waiting agent over WebSocket.
  6. The CI step exits 0 — TUNNEL UP · tun=172.16.77.1 · daemon started (pid 12483). The pipeline continues to the next step.
  7. Subsequent steps that require access to internal services over the VPN succeed.
  8. On cleanup, the app shows the agent as connected and the operator can disconnect it remotely with one tap.

Try it now — no account required

The public demo token lets you test the full relay flow immediately against the live relay backend. No registration, no credit card.

Demo token: default

Run on any Linux host (or CI runner) with openlawsvpn-cli installed:

openlawsvpn-cli -relay default -daemon

Then open the Android app → Relay tab → enter token default → tap Save & Refresh. Your agent appears in the list. Tap Connect to complete the SAML flow.

Demo limits: 10-minute session maximum · 5 concurrent agents · shared public namespace. For longer sessions or private organisation tokens, vote for extended plans on GitHub.

GitHub Actions example

- name: Start relay agent
  env:
    RELAY_TOKEN: ${{ secrets.RELAY_TOKEN }}
  run: |
    sudo openlawsvpn-cli \
      -relay "$RELAY_TOKEN" \
      -daemon \
      -pidfile /tmp/vpn.pid \
      -logfile /tmp/vpn.log

- name: Check internal service health
  run: curl -f https://internal.example.com/health

- name: Disconnect VPN
  if: always()
  run: |
    kill "$(cat /tmp/vpn.pid)" || true

Store your organisation relay token as a repository secret RELAY_TOKEN. No VPN credentials, no AWS keys, no SAML tokens in CI.

Use cases

🔄

GitHub Actions / GitLab CI

Integration tests, database migrations, internal API calls — any CI step that needs your private VPC. The relay agent starts as a step; a team member approves from their phone before the run begins.

☁️

Remote development VMs

Cloud dev boxes and jump hosts behind NAT can't open a browser. Relay lets you bring up the VPN tunnel on any remote machine with a single CLI command.

☸️

Kubernetes init containers

Run the relay agent as a VPN init container in your pod spec. The tunnel is ready before your application container starts — no sidecar complexity.

🌐

IoT and edge gateways

Embedded Linux devices can register as relay agents. A fleet manager approves tunnels from a single mobile session, granting temporary VPN access to field devices.

Ready to try it?

Install the client, run with -relay default to test immediately — no account required.