connect-pomerium
v2.0.1
Published
Automate Pomerium tunnel creation for testing, CI/CD, and automation (requires pomerium-cli v0.29.0+)
Maintainers
Readme
connect-pomerium
Automate Pomerium tunnel creation for testing, CI/CD, and automation
connect-pomerium is a lightweight TypeScript/JavaScript library that simplifies the creation and management of Pomerium tunnels. It's designed specifically for automation scenarios where you need programmatic access to Pomerium-protected services without manual browser interaction.
Requirements
- Node.js 20.0.0 or higher
- Pomerium CLI v0.29.0 or later (Download)
Note: This library requires pomerium-cli v0.29.0+ which uses JSON logging. If you're using an older CLI version, please use
[email protected]instead.
Features
- 🚀 Zero runtime dependencies - Lean and fast
- 🔐 Flexible authentication - Manual browser auth or automated (Playwright, Puppeteer, etc.)
- 🔄 Auto-reconnect support - Configurable reconnection for long-running processes
- 🎯 TypeScript-first - Full type safety and IntelliSense support
- 🖥️ Cross-platform - Works on Windows, macOS (Intel & ARM), and Linux
- 📦 Bundled binaries - Pomerium CLI included, no external dependencies
- 🎨 Lifecycle hooks - Monitor connection state with callbacks
- ⚡ Simple API - Get started with just 3 lines of code
Installation
npm install connect-pomeriumQuick Start
Basic Usage (Manual Auth)
import { PomeriumTunnel } from 'connect-pomerium';
const tunnel = new PomeriumTunnel({
targetHost: 'tcp+https://db.corp.pomerium.io:5432',
listenPort: 5432,
});
await tunnel.start(); // Browser opens for auth
console.log('Connected! Access your service at localhost:5432');
// Your automation code here...
await tunnel.stop();Automated Auth (Playwright Example)
import { chromium } from 'playwright';
import { PomeriumTunnel } from 'connect-pomerium';
const tunnel = new PomeriumTunnel({
targetHost: 'tcp+https://api.corp.pomerium.io:443',
listenPort: 8443,
onAuthRequired: async (authUrl) => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(authUrl);
await page.fill('#username', process.env.USERNAME!);
await page.fill('#password', process.env.PASSWORD!);
await page.click('button[type="submit"]');
await browser.close();
},
});
await tunnel.start(); // No browser popup, fully automatedConfiguration Options
interface PomeriumTunnelConfig {
// Required
targetHost: string; // Pomerium target (e.g., 'tcp+https://host:port')
listenPort: number; // Local port to listen on
// Optional
cliPath?: string; // Custom path to Pomerium CLI binary
onAuthRequired?: (url: string) => Promise<void>; // Auth handler
autoReconnect?: boolean; // Enable auto-reconnect (default: false)
maxReconnectAttempts?: number; // Max reconnection attempts (default: 0 = infinite)
reconnectDelay?: number; // Delay between retries in ms (default: 5000)
connectionTimeout?: number; // Connection timeout in ms (default: 60000)
// Lifecycle hooks
onConnected?: () => void;
onDisconnected?: () => void;
onReconnecting?: (attempt: number) => void;
onReconnectFailed?: () => void;
onError?: (error: Error) => void;
// NEW in v2.0.0
onLog?: (log: PomeriumLogEntry) => void; // Access raw CLI logs
logLevel?: 'debug' | 'info' | 'warn' | 'error'; // CLI log level (default: 'info')
debug?: boolean; // Enable stderr logging (default: false)
}API Reference
PomeriumTunnel
constructor(config: PomeriumTunnelConfig)
Creates a new tunnel instance with the specified configuration.
async start(): Promise<void>
Starts the tunnel and waits for connection establishment. If onAuthRequired is provided, the browser will be suppressed and your callback will handle authentication. Otherwise, Pomerium will open the browser for manual login.
Throws:
ConnectionTimeoutError- If connection times outProcessStartError- If the Pomerium process fails to startProcessExitError- If the process exits unexpectedly
async stop(): Promise<void>
Stops the tunnel and cleans up resources.
getState(): TunnelState
Returns the current tunnel state.
interface TunnelState {
connected: boolean;
reconnecting: boolean;
reconnectAttempts: number;
}isConnected(): boolean
Returns true if the tunnel is currently connected.
Examples
The examples/ directory contains complete working examples:
- 01-basic-manual-auth.ts - Simple usage with manual browser authentication
- 02-okta-playwright.ts - Automated Okta login using Playwright
- 03-google-puppeteer.ts - Automated Google SSO using Puppeteer
- 04-ci-cd-auto-reconnect.ts - CI/CD usage with auto-reconnect
To run an example:
# Install dependencies for the example
npm install playwright # or puppeteer
# Run the example
npx tsx examples/02-okta-playwright.tsUse Cases
CI/CD Pipelines
Run integration tests against Pomerium-protected services:
const tunnel = new PomeriumTunnel({
targetHost: 'tcp+https://staging-api.corp.pomerium.io:443',
listenPort: 8443,
autoReconnect: true,
maxReconnectAttempts: 5,
onAuthRequired: async (url) => {
// Use service account credentials
await automateLogin(url);
},
});
await tunnel.start();
await runIntegrationTests();
await tunnel.stop();Local Development
Access remote databases or services during development:
const tunnel = new PomeriumTunnel({
targetHost: 'tcp+https://prod-db.corp.pomerium.io:5432',
listenPort: 5432,
});
await tunnel.start(); // Manual auth in browser
// Now connect to localhost:5432 with your database clientAutomated Scripts
Build automation tools that interact with protected services:
const tunnel = new PomeriumTunnel({
targetHost: 'tcp+https://admin-api.corp.pomerium.io:443',
listenPort: 8443,
onAuthRequired: automateAuth,
});
await tunnel.start();
await performAdminTasks();
await tunnel.stop();Authentication
Manual Authentication (Default)
If you don't provide onAuthRequired, Pomerium will open your default browser for authentication. This is the simplest approach for local development.
Automated Authentication
For CI/CD and automation, provide an onAuthRequired callback. The library supports any browser automation tool:
With Playwright
npm install playwrightonAuthRequired: async (authUrl) => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(authUrl);
// Your auth logic...
await browser.close();
}With Puppeteer
npm install puppeteeronAuthRequired: async (authUrl) => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(authUrl);
// Your auth logic...
await browser.close();
}With Any Other Tool
The callback just needs to handle the authentication flow. You can use any tool that can interact with web pages.
Auto-Reconnect
By default, auto-reconnect is disabled (autoReconnect: false). This is intentional for automation scenarios where you want explicit control over failures.
To enable auto-reconnect:
const tunnel = new PomeriumTunnel({
targetHost: 'tcp+https://api.corp.pomerium.io:443',
listenPort: 8443,
autoReconnect: true,
maxReconnectAttempts: 3, // 0 = infinite
reconnectDelay: 5000, // 5 seconds between attempts
onReconnecting: (attempt) => {
console.log(`Reconnecting (${attempt}/3)...`);
},
onReconnectFailed: () => {
console.error('Failed to reconnect');
process.exit(1);
},
});Advanced Features (v2.0.0+)
Debug Logging
Control the verbosity of pomerium-cli logs:
const tunnel = new PomeriumTunnel({
targetHost: 'tcp+https://example.com:443',
listenPort: 8443,
logLevel: 'debug', // Options: 'debug', 'info', 'warn', 'error'
});Access Raw Logs
Get access to structured log entries from pomerium-cli:
const tunnel = new PomeriumTunnel({
targetHost: 'tcp+https://example.com:443',
listenPort: 8443,
onLog: (log) => {
// Log is a structured object with: level, message, component, etc.
console.log(`[${log.level.toUpperCase()}] ${log.message}`);
// Access additional fields
if (log.component) console.log(` Component: ${log.component}`);
if (log.error) console.error(` Error: ${log.error}`);
if (log['auth-url']) console.log(` Auth URL: ${log['auth-url']}`);
},
});Parse Logs Manually
You can also parse pomerium-cli logs yourself:
import { parsePomeriumLog, extractAuthUrl, type PomeriumLogEntry } from 'connect-pomerium';
// Parse JSON log line
const logEntry = parsePomeriumLog('{"level":"info","message":"connected"}');
if (logEntry) {
console.log(logEntry.level, logEntry.message);
}
// Extract auth URL from plain text
const url = extractAuthUrl('Your browser has been opened to visit:\n\nhttps://auth.example.com\n');
console.log(url); // "https://auth.example.com"Troubleshooting
Version Compatibility
If you see log parsing errors or connection issues:
Check your pomerium-cli version:
pomerium-cli --versionIf < v0.29.0:
- Update to v0.29.0+ from https://github.com/pomerium/cli/releases
- OR use
[email protected]:npm install [email protected]
If already v0.29.0+ and still having issues, please open an issue
Binary Not Found
If you see BinaryNotFoundError, the Pomerium CLI binary might not be accessible. You can provide a custom path:
const tunnel = new PomeriumTunnel({
targetHost: '...',
listenPort: 8443,
cliPath: '/custom/path/to/pomerium-cli',
});Connection Timeout
If connections timeout frequently, increase the timeout:
const tunnel = new PomeriumTunnel({
targetHost: '...',
listenPort: 8443,
connectionTimeout: 120000, // 2 minutes
});Authentication Fails
If automated authentication fails:
- Check your credentials are correct
- Verify the auth flow by running with manual auth first
- Add error handling in your
onAuthRequiredcallback - Check Pomerium CLI logs (they're output to console by default)
macOS Quarantine
On macOS, if the binary is quarantined by Gatekeeper, the library automatically removes the quarantine attribute. If you still have issues, manually run:
xattr -d com.apple.quarantine bin/pomerium-cli-darwin-*Platform Support
| Platform | Architecture | Binary Included | |----------|-------------|-----------------| | Windows | x64 | ✅ | | macOS | x64 (Intel) | ✅ | | macOS | arm64 (M1+) | ✅ | | Linux | x64 | ✅ |
TypeScript Support
This library is written in TypeScript and provides full type definitions. All configuration options, methods, and errors are fully typed.
import type { PomeriumTunnelConfig, TunnelState } from 'connect-pomerium';Error Handling
The library exports custom error classes for different failure scenarios:
import {
PomeriumError, // Base error class
ConnectionTimeoutError, // Connection establishment timeout
BinaryNotFoundError, // Pomerium CLI binary not found
ProcessExitError, // Process exited unexpectedly
ProcessStartError, // Failed to start process
AuthenticationError, // Authentication callback failed
} from 'connect-pomerium';
try {
await tunnel.start();
} catch (error) {
if (error instanceof ConnectionTimeoutError) {
console.error('Connection timed out');
} else if (error instanceof AuthenticationError) {
console.error('Auth failed');
}
}FAQ
Why no Playwright/Puppeteer dependency?
We keep the library dependency-free so you can choose your own browser automation tool (or none at all). This keeps the package size small (~10MB with binaries) instead of bloated (~200MB with Playwright).
Should I use auto-reconnect?
For CI/CD: Usually no. You want explicit failures so you can debug issues. For long-running processes: Yes. Enable it with reasonable retry limits. For local development: Depends on your workflow.
Can I create multiple tunnels?
Yes! Create multiple PomeriumTunnel instances with different ports:
const dbTunnel = new PomeriumTunnel({ targetHost: '...', listenPort: 5432 });
const apiTunnel = new PomeriumTunnel({ targetHost: '...', listenPort: 8443 });
await Promise.all([dbTunnel.start(), apiTunnel.start()]);Is this a replacement for Pomerium?
No. This library is specifically for automation scenarios (testing, CI/CD, scripts). For production Pomerium deployments, use the official Pomerium installation methods.
Development
Build
npm run buildTest
npm testLint
npm run lint
npm run formatValidate Package Exports
npm run check:exportsRelease Workflow
This package uses automated publishing via GitHub Actions.
Creating a Release
- Make your changes and commit them
- Update the version:
npm version patch # for bug fixes npm version minor # for new features npm version major # for breaking changes - Push the changes and tags:
git push && git push --tags - Package automatically publishes to npm 🎉
License
MIT © oharu121
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Issues
If you encounter any issues, please report them at GitHub Issues.
Support
- Issues: GitHub Issues
- Pomerium Docs: pomerium.com/docs
