Epithet uses external authentication plugins to obtain tokens for the CA server. This design allows epithet to work with any identity provider without needing to understand specific authentication protocols.
The broker invokes authentication plugins when certificates need to be requested. Auth plugins follow a simple protocol:
- stdin: State from previous invocation (empty on first call)
- stdout: Authentication token (raw bytes)
- fd 3: New state to persist for next invocation (max 10 MiB)
- stderr: Human-readable messages and errors
- Exit code: 0 = success, non-zero = failure
Generic OIDC/OAuth2 authentication that works with Google Workspace, Okta, Azure AD, and any OIDC-compliant identity provider.
Features:
- Authorization code flow with PKCE
- Automatic token refresh
- Dynamic port selection (no configuration needed)
- Silent browser launch for authentication
Usage:
epithet agent \
--ca-url https://ca.example.com \
--auth "epithet auth oidc \
--issuer https://accounts.google.com \
--client-id 123456.apps.googleusercontent.com \
--scopes openid,profile,email"Configuration:
| Flag | Required | Description |
|---|---|---|
--issuer |
Yes | OIDC issuer URL (e.g., https://accounts.google.com) |
--client-id |
Yes | OAuth2 client ID from your identity provider |
--client-secret |
No | OAuth2 client secret (optional if using PKCE) |
--scopes |
No | OAuth2 scopes (default: openid,profile,email) |
See OIDC Setup Guide for detailed instructions.
You can write custom authentication plugins in any language. They just need to follow the protocol:
#!/bin/bash
# Read state from stdin (unused in this example)
state=$(cat)
# Get token somehow
token=$(my-auth-command)
# Output token to stdout
echo -n "$token"
# Output state to fd 3 (empty in this example)
echo -n "" >&3#!/usr/bin/env python3
import sys
import os
import json
import requests
# Read state from stdin
state_bytes = sys.stdin.buffer.read()
state = json.loads(state_bytes) if state_bytes else {}
# Check if we have a refresh token
if 'refresh_token' in state:
# Refresh the token
response = requests.post('https://auth.example.com/token', data={
'grant_type': 'refresh_token',
'refresh_token': state['refresh_token'],
'client_id': 'client123',
})
data = response.json()
access_token = data['access_token']
new_refresh_token = data.get('refresh_token', state['refresh_token'])
else:
# Perform initial authentication
# (implementation depends on your auth system)
access_token, new_refresh_token = do_initial_auth()
# Output access token to stdout
sys.stdout.buffer.write(access_token.encode())
# Output new state to fd 3
state_fd = os.fdopen(3, 'wb')
state_fd.write(json.dumps({
'refresh_token': new_refresh_token
}).encode())
state_fd.close()-
First SSH connection:
- Broker has no token
- Broker invokes auth plugin with empty stdin
- Plugin performs full authentication (browser flow, etc.)
- Plugin returns token and state
- Broker uses token to request certificate from CA
-
Subsequent connections (certificate expired):
- Broker invokes auth plugin with previous state on stdin
- Plugin uses refresh token from state to get new access token
- Plugin returns new token and updated state
- Broker uses token to request certificate from CA
-
CA returns 401 Unauthorized:
- Broker clears current token
- Broker invokes auth plugin (may use refresh token from state)
- Plugin returns fresh token
- Broker retries certificate request
- State never touches disk: Broker stores state in memory only
- 10 MiB state limit: Prevents memory exhaustion from buggy plugins
- Tokens are opaque: Broker doesn't parse or understand token format
- Browser-based auth: User authenticates in their browser, not in terminal
- Refresh tokens: Long-lived sessions without repeated browser auth
Check the auth plugin's stderr output for details. Common issues:
- Invalid client ID or secret
- Incorrect issuer URL
- User cancelled authentication in browser
- Network connectivity issues
The refresh token may have expired or been revoked. This triggers a new full authentication flow. If it continues failing:
- Check that
--scopesincludes the scopes needed by your CA - Verify the OAuth app is still active in your identity provider
- Check the CA server logs for token validation errors
For provider-specific details, see the OIDC setup guide.