wmantly 255835af7a
Pull Request Tests / Run Tests (18.x) (push) Successful in 49s
Pull Request Tests / Run Tests (20.x) (push) Successful in 40s
Pull Request Tests / Run Tests (22.x) (push) Successful in 41s
Pull Request Tests / Test Summary (push) Successful in 4s
feat: edit permission entries (v1.35.0)
The Permissions page only offered Delete, so changing a role or scope
meant removing the grant and re-adding it from memory.

A permission's id is derived from (subjectType, subject, scope, domain),
so changing any of those is a different record rather than an update. The
new PUT creates the new grant and removes the superseded one in that
order, so an edit can never leave the old grant behind still conferring
access.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:34:11 -04:00
2025-12-31 16:54:49 -05:00
2024-08-13 01:29:57 +08:00

Proxy

A reverse proxy and HTTPS termination service built on OpenResty/nginx, with a management API and web GUI. It puts any of your apps behind single sign-on (OIDC) and can also look users up directly in LDAP — so the same people who log in to your SSO are the people allowed to reach your proxied apps.

It handles the parts that are tedious to do by hand: automatic HTTPS certificates from Let's Encrypt (including wildcards via DNS-01), routing by hostname with wildcard matching, and per-host access control tied to your identity provider. You manage hosts, DNS providers, and permissions from a web UI or a REST API; the proxy serves them over TLS with auto-renewing certs and no downtime on changes.

Running it together with the theta42 SSO Manager? theta-env composes the two with one setup.sh — it generates the OIDC + LDAP wiring from a single setup.env so the proxy and the SSO find each other without manual config.

Documentation: https://theta42.github.io/proxy/ (CHANGELOG.md for what changed in each release) — also readable from the running app itself at /docs, no internet access required.

Screenshots

Hosts Authentication
Host list Per-host SSO auth

Basic auth and SSO are mutually exclusive per host, with per-user password management once basic auth is enabled:

Per-host basic auth

Multiple backend targets per host, load balanced round-robin:

Load balancing

Why this over the alternatives

Nginx Proxy Manager, Traefik, and Caddy are all good reverse proxies with auto-HTTPS. This one is built around identity: it is both an OIDC client of an SSO provider (for browser login) and a direct LDAP client (for user lookups and per-host access control), so access decisions come from your real user directory, not a static allow-list or a separate auth proxy bolted on top. The trade-off is that it expects an OIDC/LDAP identity source to point at — it is not a standalone auth server. Pair it with the theta42 SSO Manager (bundled OpenLDAP + OIDC) for a self-hosted SSO + proxy stack, or point it at any OIDC provider + LDAP directory you already run.

Features

  • Automated HTTPS/SSL certificate management via Let's Encrypt
  • Support for HTTP-01 (auto-ssl) and DNS-01 (wildcard) ACME challenges
  • Multiple DNS provider integrations (Cloudflare, DigitalOcean, PorkBun, DuckDNS — DuckDNS is free)
  • Wildcard SSL certificate support with automatic renewal
  • Dynamic host routing with wildcard domain matching (*, **)
  • Multi-target load balancing — configure multiple backend targets per host with built-in round-robin load balancing
  • Web-based management interface
  • RESTful API for automation
  • OIDC login — the proxy is an OpenID Connect client of an external SSO (e.g. theta42/sso-manager-node)
  • Direct LDAP lookups, independent of the OIDC flow
  • Role-based access control (RBAC) — global admins, local groups, and per-domain permissions (viewer/manager) via /api/permission and /api/group
  • Self-service API tokens (PATs) for scripting/CI without a browser session
  • Unix socket-based host lookup for high-performance routing

Requirements

  • Node.js 18+ (tested with 18.x, 20.x, 22.x)
  • OpenResty (nginx with Lua support)
  • Redis
  • Modern Linux distribution (tested on Ubuntu 20.04+, Debian 11+)
  • Inbound internet access for Let's Encrypt validation
  • Root access (required for user management features)

Quick start

Three ways to run it, in order of how much it sets up for you:

theta-env composes this proxy with the theta42 SSO Manager and generates all the wiring (OIDC endpoints, LDAP bind, hostnames) from a single setup.env. You enter your domain once and the proxy is registered as an OIDC client of the SSO automatically:

git clone --recursive https://github.com/theta42/theta-env.git
cd theta-env
cp setup.env.example setup.env   # set CFG_DOMAIN to your domain
./setup.sh                       # generates ./config/, builds + bootstraps + starts both

See the theta-env README for the full first-run flow, DNS/port requirements, and backups.

2. Standalone, in Docker

The all-in-one image bundles OpenResty, the Node management app, and Redis. The OIDC/LDAP/auth config is read from a bind-mounted ./config/proxy-secrets.js (the OAuth client id/secret are filled in by your SSO's bootstrap, or you set them yourself):

git clone https://github.com/theta42/proxy.git
cd proxy
mkdir -p config && chmod 700 config
cp secrets.js.example config/proxy-secrets.js
$EDITOR config/proxy-secrets.js   # point oidc/ldap at your SSO + directory
docker compose up -d --build

The management UI comes up at http://127.0.0.1:3000 (bound to localhost; the OpenResty front proxies it under TLS on 443). See DEPLOYMENT.md and secrets.js.example for the full config shape.

3. Bare metal (Debian/Ubuntu)

An automated installer installs Node.js, OpenResty, Redis, the Lua modules, and the app, then symlinks the nginx config and starts a systemd service:

wget -O - https://raw.githubusercontent.com/theta42/proxy/master/ops/install.sh | sudo bash

This installer will:

  • Install Node.js 22.x
  • Install OpenResty and required dependencies
  • Install and configure Redis
  • Set up SSL fallback certificates
  • Install Lua dependencies (lua-resty-auto-ssl, luasocket)
  • Clone/update the proxy application at /opt/theta42/proxy
  • Seed /etc/proxy/secrets.js on first run (edit it, then re-run or systemctl restart proxy)
  • Configure systemd service
  • Start the proxy service

It's idempotent and safe to re-run — re-running it updates the app in place and prints the version you're updating from and to (e.g. Updated v1.1.13 -> v1.1.14), or Already up to date if there's nothing new.

Logs (Docker)

The all-in-one image runs OpenResty in the foreground and the Node app in the background, both writing to the container's stdout/stderr. The proxy's nginx access/error logs go to files (/var/log/nginx, on the proxy-logs volume), so they do not show up in docker logs.

# App + OpenResty (stdout/stderr)
docker compose logs -f proxy
# or, by container name:
docker logs -f proxy

# nginx access / error logs (in-container files, not in docker logs)
docker compose exec proxy tail -f /var/log/nginx/access.log
docker compose exec proxy tail -f /var/log/nginx/error.log

# Recent context
docker compose logs --tail=200 --since=10m proxy

Secrets

Secrets are loaded from OpenBao at boot via @simpleworkjs/bao-conf, which deep-merges secret/proxy/conf over the file-loaded config. The proxy's OIDC clientSecret is captured at require time (inside createOidcClient during require('../models')), so bin/www runs bao-conf.init() before require('../app') (which transitively loads models). Fail-soft: if OpenBao is unreachable, boot continues from CONF_SECRETS. The proxy authenticates to OpenBao with the scoped VAULT_TOKEN (env, policy proxy — read only secret/proxy/conf), never the root token.

The config/proxy-secrets.js file is an operator-edit seed artifact (gitignored); the bootstrap writes the generated OAuth client creds into OpenBao, which is authoritative. For the full architecture see theta-env's Secrets docs.

Manual Installation

For manual installation or other distributions, see the detailed steps below.

Recommended path: ops/install.sh is idempotent and safe to re-run — it symlinks the OpenResty/systemd config from the repo checkout, so updates stay in sync automatically. The manual steps below copy those same files instead of symlinking them, so they will not auto-track future changes to ops/nginx_conf/ or ops/proxy.service — you'd need to re-copy them yourself after every update. Use the manual path only if install.sh doesn't fit your distribution.

System Dependencies

Ubuntu/Debian:

apt install libpam0g-dev build-essential redis-server luarocks -y

Node.js 22.x:

curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=22
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
apt update && apt install nodejs -y

OpenResty:

wget -O - https://openresty.org/package/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/openresty.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list
apt update && apt install openresty -y

Lua Dependencies:

luarocks install lua-resty-auto-ssl
luarocks install luasocket

SSL Configuration

Create fallback SSL certificates:

mkdir -p /etc/ssl/
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
  -subj '/CN=sni-support-required-for-valid-ssl' \
  -keyout /etc/ssl/resty-auto-ssl-fallback.key \
  -out /etc/ssl/resty-auto-ssl-fallback.crt

OpenResty Configuration

Configuration files are provided in ops/nginx_conf/:

  • nginx.conf - Main nginx configuration
  • autossl.conf - Auto-SSL configuration for Let's Encrypt HTTP-01
  • proxy.conf - Proxy server configuration with host lookup
  • targetinfo.lua - Lua module for host lookup via Unix socket

Copy these files to /etc/openresty/:

mkdir -p /etc/openresty/sites-enabled/
cp ops/nginx_conf/nginx.conf /etc/openresty/nginx.conf
cp ops/nginx_conf/autossl.conf /etc/openresty/autossl.conf
cp ops/nginx_conf/proxy.conf /etc/openresty/sites-enabled/000-proxy
cp ops/nginx_conf/targetinfo.lua /usr/local/openresty/lualib/targetinfo.lua

Application Setup

Clone and install:

mkdir -p /opt/theta42
cd /opt/theta42
git clone https://github.com/theta42/proxy.git
cd proxy/nodejs
npm install

Configure secrets:

mkdir -p /etc/proxy
cp ../secrets.js.example /etc/proxy/secrets.js
chmod 600 /etc/proxy/secrets.js
$EDITOR /etc/proxy/secrets.js

Create systemd service:

cp ../ops/proxy.service /etc/systemd/system/proxy.service
systemctl daemon-reload
systemctl enable proxy.service
systemctl start proxy.service

DNS Provider Configuration

For wildcard SSL certificates, configure a DNS provider via the web UI or API:

Supported providers:

  • Cloudflare - Requires API token
  • DigitalOcean - Requires API token
  • PorkBun - Requires API key and secret API key
  • DuckDNS - Free. Requires your account token and the list of subdomains you've registered at duckdns.org (e.g. myhost for myhost.duckdns.org). Good option if you don't own a domain — DuckDNS gives you one for free. Note DuckDNS only supports a single A/AAAA record and a single TXT record per domain (no arbitrary subdomains), which is enough for both dynamic DNS and DNS-01 wildcard certs but not for hosting other DNS records.

Once configured, create a wildcard host (e.g., *.example.com) and the system will automatically request and manage the DNS-01 challenge certificate.

Architecture

The system consists of three main components:

  1. OpenResty/Nginx - Frontend proxy with Lua-based routing

    • Handles SSL termination via lua-resty-auto-ssl
    • Queries Node.js backend via Unix socket for host routing
    • Proxies requests to configured backend servers
  2. Node.js API - Backend management and control plane

    • RESTful API for host/user/DNS management
    • Wildcard SSL certificate orchestration
    • Host lookup tree with wildcard matching
    • User authentication and authorization
  3. Redis - Data store (using model-redis ORM)

    • Host configurations
    • User accounts and tokens
    • SSL certificate storage
    • Domain and DNS provider configurations

Host Lookup System

The proxy supports sophisticated domain matching:

  • Exact match: example.com matches only example.com
  • Single wildcard: *.example.com matches sub.example.com but not deep.sub.example.com
  • Double wildcard: **.example.com matches any depth (sub.example.com, deep.sub.example.com, etc.)
  • Mixed wildcards: api.*.example.com matches api.v1.example.com, api.v2.example.com, etc.

Priority: Exact match > Single wildcard > Double wildcard

Development

Running locally:

cd nodejs
npm install
npm run dev  # Runs with nodemon for auto-reload

Running tests:

npm test                  # Run all tests
npm run test:unit         # Run unit tests only
npm run test:integration  # Run integration tests only
npm run test:watch        # Watch mode for development

Tests use Node.js built-in test runner (requires Node 18+).

API Documentation

See API Documentation for complete API reference.

Contributing

Pull requests are welcome. The project uses GitHub Actions for CI/CD:

  • Tests run automatically on all PRs
  • All tests must pass before merging to master
  • Tests run on Node.js 18.x, 20.x, and 22.x

License

MIT - See LICENSE file for details.

Project Structure

proxy/
├── nodejs/              # Node.js backend application
│   ├── bin/            # Entry point (www)
│   ├── conf/           # Configuration (base.js, environment overlays, secrets.js)
│   ├── controller/      # App-level wiring (pubsub, startup)
│   ├── migrations/      # One-off Redis data migration scripts
│   ├── models/         # Data models (Host, User, DNS providers)
│   ├── routes/         # API routes
│   ├── services/       # Background services (host lookup, scheduler)
│   ├── middleware/     # Express middleware
│   ├── utils/          # Utility functions
│   ├── public/         # Static web assets
│   ├── views/          # EJS templates
│   └── test/           # Test suite
├── ops/                # Operations and deployment
│   ├── nginx_conf/     # OpenResty configuration files
│   ├── install.sh      # Automated installer
│   └── proxy.service   # Systemd service definition
└── .github/workflows/  # CI/CD workflows
S
Description
Simple API and Web front end to configure HTTP/S proxy, auto lets encrypt.
Readme 4.4 MiB
Languages
JavaScript 68.6%
EJS 20.6%
Shell 4.5%
Lua 2.9%
Dockerfile 2.1%
Other 1.3%