From 0300039950cc4fbddc1bd49a38b04be43bbce983 Mon Sep 17 00:00:00 2001 From: essinghigh <110120257+essinghigh@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:59:46 +0000 Subject: [PATCH 1/7] Refactor radius-proxy into standalone ts-radius-client library --- .github/workflows/ci.yml | 67 -- .github/workflows/publish-image.yml.disabled | 42 - .gitignore | 35 +- .vscode/mcp.json | 11 - README.md | 71 +- bun.lock | 25 + docker-compose.yml | 21 - docs/01-introduction/01-overview.md | 41 - docs/01-introduction/02-architecture.md | 89 -- docs/02-getting-started/01-installation.md | 130 --- docs/02-getting-started/02-docker-setup.md | 127 --- .../03-configuration/01-main-configuration.md | 202 ---- .../02-environment-variables.md | 83 -- docs/04-features/01-authentication-flow.md | 149 --- docs/04-features/02-radius-failover.md | 74 -- docs/04-features/03-grafana-integration.md | 98 -- docs/04-features/04-security.md | 70 -- docs/05-api-reference/01-oauth-endpoints.md | 134 --- docs/05-api-reference/02-oidc-discovery.md | 139 --- docs/06-guides/01-deployment.md | 112 --- docs/06-guides/02-development.md | 87 -- docs/06-guides/03-troubleshooting.md | 85 -- docs/README.md | 36 - package.json | 16 + radius-proxy/.dockerignore | 14 - radius-proxy/.gitignore | 48 - radius-proxy/Dockerfile | 36 - .../app/api/.well-known/jwks.json/route.ts | 56 -- .../.well-known/openid-configuration/route.ts | 47 - radius-proxy/app/api/oauth/authorize/route.ts | 257 ----- radius-proxy/app/api/oauth/token/route.ts | 267 ------ .../app/api/oauth/userinfo/emails/route.ts | 24 - radius-proxy/app/api/oauth/userinfo/route.ts | 20 - radius-proxy/app/favicon.ico | Bin 1150 -> 0 bytes radius-proxy/app/globals.css | 122 --- radius-proxy/app/layout.tsx | 36 - radius-proxy/app/page.tsx | 35 - radius-proxy/components.json | 22 - radius-proxy/components/login-form.tsx | 153 --- radius-proxy/components/ui/button.tsx | 60 -- radius-proxy/components/ui/card.tsx | 92 -- radius-proxy/components/ui/field.tsx | 244 ----- radius-proxy/components/ui/input.tsx | 21 - radius-proxy/components/ui/label.tsx | 24 - radius-proxy/components/ui/separator.tsx | 28 - radius-proxy/components/ui/sonner.tsx | 41 - radius-proxy/config.example.toml | 59 -- radius-proxy/eslint.config.mjs | 25 - radius-proxy/global.d.ts | 14 - radius-proxy/lib/access.ts | 10 - radius-proxy/lib/config.ts | 330 ------- radius-proxy/lib/grafana.ts | 224 ----- radius-proxy/lib/index.ts | 8 - radius-proxy/lib/jwt.ts | 116 --- radius-proxy/lib/log.ts | 236 ----- radius-proxy/lib/radius.ts | 249 ----- radius-proxy/lib/radius_hosts.ts | 183 ---- radius-proxy/lib/scopes.ts | 36 - radius-proxy/lib/server-utils.ts | 98 -- radius-proxy/lib/storage.ts | 130 --- radius-proxy/lib/utils.ts | 6 - radius-proxy/next.config.js | 8 - radius-proxy/package.json | 41 - radius-proxy/postcss.config.mjs | 5 - radius-proxy/public/grafana-logo.svg | 57 -- radius-proxy/tests/auth.test.ts | 878 ------------------ radius-proxy/tests/radius_hosts.test.ts | 47 - radius-proxy/tsconfig.json | 27 - src/client.ts | 229 +++++ src/index.ts | 3 + src/protocol.ts | 238 +++++ src/types.ts | 53 ++ tests/client.test.ts | 105 +++ tsconfig.json | 25 + 74 files changed, 796 insertions(+), 6235 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/publish-image.yml.disabled delete mode 100644 .vscode/mcp.json create mode 100644 bun.lock delete mode 100644 docker-compose.yml delete mode 100644 docs/01-introduction/01-overview.md delete mode 100644 docs/01-introduction/02-architecture.md delete mode 100644 docs/02-getting-started/01-installation.md delete mode 100644 docs/02-getting-started/02-docker-setup.md delete mode 100644 docs/03-configuration/01-main-configuration.md delete mode 100644 docs/03-configuration/02-environment-variables.md delete mode 100644 docs/04-features/01-authentication-flow.md delete mode 100644 docs/04-features/02-radius-failover.md delete mode 100644 docs/04-features/03-grafana-integration.md delete mode 100644 docs/04-features/04-security.md delete mode 100644 docs/05-api-reference/01-oauth-endpoints.md delete mode 100644 docs/05-api-reference/02-oidc-discovery.md delete mode 100644 docs/06-guides/01-deployment.md delete mode 100644 docs/06-guides/02-development.md delete mode 100644 docs/06-guides/03-troubleshooting.md delete mode 100644 docs/README.md create mode 100644 package.json delete mode 100644 radius-proxy/.dockerignore delete mode 100644 radius-proxy/.gitignore delete mode 100644 radius-proxy/Dockerfile delete mode 100644 radius-proxy/app/api/.well-known/jwks.json/route.ts delete mode 100644 radius-proxy/app/api/.well-known/openid-configuration/route.ts delete mode 100644 radius-proxy/app/api/oauth/authorize/route.ts delete mode 100644 radius-proxy/app/api/oauth/token/route.ts delete mode 100644 radius-proxy/app/api/oauth/userinfo/emails/route.ts delete mode 100644 radius-proxy/app/api/oauth/userinfo/route.ts delete mode 100644 radius-proxy/app/favicon.ico delete mode 100644 radius-proxy/app/globals.css delete mode 100644 radius-proxy/app/layout.tsx delete mode 100644 radius-proxy/app/page.tsx delete mode 100644 radius-proxy/components.json delete mode 100644 radius-proxy/components/login-form.tsx delete mode 100644 radius-proxy/components/ui/button.tsx delete mode 100644 radius-proxy/components/ui/card.tsx delete mode 100644 radius-proxy/components/ui/field.tsx delete mode 100644 radius-proxy/components/ui/input.tsx delete mode 100644 radius-proxy/components/ui/label.tsx delete mode 100644 radius-proxy/components/ui/separator.tsx delete mode 100644 radius-proxy/components/ui/sonner.tsx delete mode 100644 radius-proxy/config.example.toml delete mode 100644 radius-proxy/eslint.config.mjs delete mode 100644 radius-proxy/global.d.ts delete mode 100644 radius-proxy/lib/access.ts delete mode 100644 radius-proxy/lib/config.ts delete mode 100644 radius-proxy/lib/grafana.ts delete mode 100644 radius-proxy/lib/index.ts delete mode 100644 radius-proxy/lib/jwt.ts delete mode 100644 radius-proxy/lib/log.ts delete mode 100644 radius-proxy/lib/radius.ts delete mode 100644 radius-proxy/lib/radius_hosts.ts delete mode 100644 radius-proxy/lib/scopes.ts delete mode 100644 radius-proxy/lib/server-utils.ts delete mode 100644 radius-proxy/lib/storage.ts delete mode 100644 radius-proxy/lib/utils.ts delete mode 100644 radius-proxy/next.config.js delete mode 100644 radius-proxy/package.json delete mode 100644 radius-proxy/postcss.config.mjs delete mode 100644 radius-proxy/public/grafana-logo.svg delete mode 100644 radius-proxy/tests/auth.test.ts delete mode 100644 radius-proxy/tests/radius_hosts.test.ts delete mode 100644 radius-proxy/tsconfig.json create mode 100644 src/client.ts create mode 100644 src/index.ts create mode 100644 src/protocol.ts create mode 100644 src/types.ts create mode 100644 tests/client.test.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 45af20b..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: ci - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - -jobs: - format: - if: github.repository_owner == 'essinghigh-org' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ github.head_ref }} - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - name: Install formatters - run: pip install black isort - - name: Format code - run: black . && isort --profile black . - - name: Commit changes - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add . - if git diff --staged --quiet; then - echo "No changes to commit" - else - git commit -m "style: formatting" - git push - fi - opencode: - if: | - github.repository_owner == 'essinghigh-org' && - (github.event_name == 'pull_request' || - contains(github.event.comment.body, '/oc') || contains(github.event.comment.body, '/opencode')) - runs-on: ubuntu-latest - needs: format - permissions: - id-token: write - contents: write - pull-requests: write - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - persist-credentials: false - - - name: Run opencode - uses: anomalyco/opencode/github@latest - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - with: - model: opencode/minimax-m2.5-free - prompt: | - Review this pull request: - - Check for code quality issues - - Look for potential bugs - - Suggest improvements diff --git a/.github/workflows/publish-image.yml.disabled b/.github/workflows/publish-image.yml.disabled deleted file mode 100644 index 20a17a8..0000000 --- a/.github/workflows/publish-image.yml.disabled +++ /dev/null @@ -1,42 +0,0 @@ -name: Publish container image - -on: - push: - branches: [ main ] - -permissions: - contents: read - packages: write - -jobs: - build-and-push: - if: ${{ github.event.head_commit && !contains(github.event.head_commit.message, 'skip_publish') }} - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push image - uses: docker/build-push-action@v5 - with: - context: ./radius-proxy - file: ./radius-proxy/Dockerfile - push: true - tags: ghcr.io/${{ github.repository_owner }}/radius-proxy:latest - platforms: linux/amd64 - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 63d7577..a14702c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,34 @@ +# dependencies (bun install) node_modules -bun.lock -package-lock.json \ No newline at end of file + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/.vscode/mcp.json b/.vscode/mcp.json deleted file mode 100644 index 6716ff9..0000000 --- a/.vscode/mcp.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "servers": { - "shadcn": { - "command": "npx", - "args": [ - "shadcn@latest", - "mcp" - ] - } - } -} diff --git a/README.md b/README.md index f5d709a..9733234 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,70 @@ -# Grafana OAUTH2 to RADIUS Login +# ts-radius-client -image \ No newline at end of file +A standards-compliant RADIUS client for TypeScript/Bun, extracted from a production OAuth2 proxy. + +## Features + +- **Standards Compliant**: Supports RFC 2865 (PAP authentication) and RFC 2869. +- **Failover**: Automatic failover to backup hosts on timeout. +- **Health Checks**: Background health checks to restore primary hosts. +- **Configurable**: extensive configuration for timeouts, retries, and attribute extraction. +- **TypeScript**: Written in TypeScript with full type definitions. +- **Bun**: Optimized for Bun runtime. + +## Installation + +Install directly from GitHub: + +```bash +bun add github:essinghigh-org/ts-radius-client +``` + +## Usage + +```typescript +import { RadiusClient, RadiusConfig } from "ts-radius-client"; + +const config: RadiusConfig = { + host: "10.0.0.1", + hosts: ["10.0.0.1", "10.0.0.2"], // Failover hosts + secret: "my-shared-secret", + timeoutMs: 5000, +}; + +const client = new RadiusClient(config); + +try { + const result = await client.authenticate("username", "password"); + + if (result.ok) { + console.log("Authentication successful!"); + if (result.class) { + console.log("Received Class attribute:", result.class); + } + } else { + console.log("Authentication failed:", result.error || "Unknown error"); + } +} catch (err) { + console.error("Client error:", err); +} finally { + // Clean up timers if shutting down the app + client.shutdown(); +} +``` + +## Configuration (`RadiusConfig`) + +| Option | Type | Default | Description | +|Prefix|---|---|---| +| `host` | `string` | (Required) | Primary RADIUS host IP/hostname. | +| `hosts` | `string[]` | `[host]` | Ordered list of hosts for failover. | +| `secret` | `string` | (Required) | Shared secret. | +| `port` | `number` | `1812` | RADIUS port. | +| `timeoutMs` | `number` | `5000` | Request timeout in milliseconds. | +| `healthCheckIntervalMs` | `number` | `1800000` | (30m) Interval for background health checks. | +| `healthCheckUser` | `string` | `grafana_dummy_user` | Username for health probes. | +| `assignmentAttributeId` | `number` | `25` | Attribute ID to extract (e.g., 25 for Class). | + +## License + +MIT diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..7f9d89d --- /dev/null +++ b/bun.lock @@ -0,0 +1,25 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "/app", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + + "@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="], + + "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + } +} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index acc1b4e..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: '3.8' - -services: - radius-proxy: - image: ghcr.io/essinghigh/radius-proxy:latest - restart: unless-stopped - ports: - - "${HOST_PORT:-54567}:54567" - environment: - - NODE_ENV=production - - ISSUER=http://auth.example.local:${HOST_PORT:-54567} - - EMAIL_SUFFIX=example.com - - RADIUS_HOST=radius.example.local - - RADIUS_SECRET=secret - - RADIUS_PORT=1812 - - REDIRECT_URIS= - volumes: - - type: bind - source: ./config.toml - target: /app/config.toml - read_only: true diff --git a/docs/01-introduction/01-overview.md b/docs/01-introduction/01-overview.md deleted file mode 100644 index 8bd460e..0000000 --- a/docs/01-introduction/01-overview.md +++ /dev/null @@ -1,41 +0,0 @@ -# Overview - -**`radius-proxy`** is a specialized, high-performance authentication proxy designed to bridge the gap between modern applications that use OAuth 2.0 and OpenID Connect (OIDC) and traditional IT infrastructure that relies on the RADIUS protocol for authentication. - -Its primary use case is to serve as a **Generic OAuth 2.0 Provider for Grafana**, enabling users to log into Grafana using their credentials from one or more RADIUS servers. - -## The Problem - -Grafana offers a flexible authentication system that supports various providers, including its own internal user database, LDAP, and several OAuth 2.0 providers like Google, GitHub, and Azure AD. For enterprise environments that have standardized on RADIUS for network and service access control, integrating Grafana can be challenging. Grafana does not have a built-in RADIUS authentication module, and has [no intention of adding one](https://github.com/grafana/grafana/pull/111708). - -This creates a disconnect: - -- **Modern Application (Grafana):** Expects modern, token-based authentication flows like OAuth 2.0. -- **Legacy Infrastructure (RADIUS):** Provides a robust, but older, challenge-response authentication mechanism based on shared secrets and UDP packets. - -Without a bridge, administrators are forced to manage a separate user database for Grafana, defeating the purpose of a centralized authentication system. - -## The Solution - -`radius-proxy` acts as a sophisticated intermediary that speaks both languages: - -1. **To Grafana, it appears as a standard OAuth 2.0 and OIDC provider.** It exposes all the necessary endpoints that Grafana's "Generic OAuth" integration requires, including authorization, token exchange, and user information. - -2. **To the RADIUS infrastructure, it acts as a standard RADIUS client (Network Access Server).** When a user attempts to log in, the proxy constructs and sends a RADIUS `Access-Request` packet to the configured RADIUS server(s) and securely validates the user's credentials using the PAP (Password Authentication Protocol). - -By doing so, it seamlessly translates Grafana's OAuth 2.0 login request into a RADIUS authentication sequence and returns the result in a format Grafana understands. - -## Key Features - -The application is built with performance, reliability, and security in mind, offering a rich set of features: - -- **Full OAuth 2.0 `authorization_code` Grant Flow**: Implements the standard, secure authorization code flow, complete with PKCE (Proof Key for Code Exchange) support for enhanced security. -- **OpenID Connect (OIDC) Discovery**: Provides `/.well-known/openid-configuration` and `/.well-known/jwks.json` endpoints, allowing for automatic configuration and key discovery by OIDC-compliant clients like Grafana. -- **RADIUS Authentication**: Securely authenticates users against one or more RADIUS servers using the PAP protocol. -- **High-Availability RADIUS Failover**: Can be configured with multiple RADIUS servers. It performs periodic background health checks and will automatically fail over to a healthy server if the primary one becomes unresponsive, ensuring high availability. -- **Dynamic Grafana Role Mapping**: Maps RADIUS `Class` attributes (or other configurable attributes) to Grafana roles. For example, a user with a specific RADIUS group can be automatically granted `GrafanaAdmin` privileges. -- **Automatic Grafana Team Synchronization**: Automatically adds users to specific Grafana teams based on their RADIUS group memberships. This is achieved by mapping RADIUS `Class` attributes to Grafana Team IDs. -- **Secure Token Generation**: Generates signed JSON Web Tokens (JWTs) for both ID Tokens and Access Tokens. It supports RSA (RS256) with automatic key generation and rotation, as well as HS256 for simpler setups. -- **Extensive Configuration**: All operational parameters are managed through a single `config.toml` file, with support for environment variable overrides for easy deployment in containerized environments. -- **Lightweight and Performant**: Built on Next.js and Bun, the application is lightweight and optimized for fast startup and low resource consumption. -- **Easy Deployment**: Can be run as a standalone Node.js application or as a Docker container, with a provided `docker-compose.yml` for quick setup. diff --git a/docs/01-introduction/02-architecture.md b/docs/01-introduction/02-architecture.md deleted file mode 100644 index 3d283a0..0000000 --- a/docs/01-introduction/02-architecture.md +++ /dev/null @@ -1,89 +0,0 @@ -# Technical Architecture - -`radius-proxy` is a [Next.js](https://nextjs.org/) application that leverages its server-side API routes to function as a self-contained OAuth 2.0 and OIDC provider. The architecture is designed to be stateless where possible, relying on a robust configuration system and in-memory storage for short-lived data. - -## High-Level Data Flow - -A typical authentication sequence involves the following steps: - -1. **Initiation**: A user in Grafana clicks the "Login with OAuth" button. Grafana, configured to use `radius-proxy`, redirects the user to the proxy's `/api/oauth/authorize` endpoint. -2. **Login UI**: The proxy presents a login form to the user. -3. **Credential Submission**: The user submits their username and password to the `/api/oauth/authorize` endpoint via a POST request. -4. **RADIUS Authentication**: The proxy, acting as a RADIUS client, sends an `Access-Request` to the active RADIUS server. This is handled by the `lib/radius_hosts.ts` manager, which ensures a healthy server is chosen. -5. **Authorization Code**: Upon successful RADIUS authentication, the proxy generates a short-lived, one-time authorization code and stores it in memory. It then redirects the user back to Grafana with this code. -6. **Token Exchange**: Grafana, in a back-channel request, sends the authorization code to the proxy's `/api/oauth/token` endpoint. -7. **Token Issuance**: The proxy validates the code, and if valid, generates and signs a JWT-based `id_token` and `access_token`. It returns these tokens to Grafana. -8. **Grafana Login**: Grafana validates the JWTs, extracts user information (like username, email, and role) from the claims, and logs the user in. -9. **Team Sync (Optional)**: After issuing the tokens, the proxy can make an API call to Grafana to automatically add the user to predefined teams based on their RADIUS attributes. - -## Core Components - -The application is composed of several key modules found in the `radius-proxy/lib/` directory. - -### 1. Configuration (`lib/config.ts`) - -The configuration manager is the heart of the application, responsible for loading, parsing, and providing access to all operational parameters. - -- **Source**: It reads settings from `config.toml` or `config.example.toml`. -- **Overrides**: Any setting can be overridden by a corresponding environment variable (e.g., `RADIUS_HOST` overrides the `RADIUS_HOST` key in the file). -- **Dynamic Reloading**: It uses a file watcher to monitor the configuration file for changes. If the file is updated, the configuration is automatically reloaded at runtime without requiring a server restart. This allows for dynamic changes to RADIUS servers, client secrets, and other parameters. -- **Type Safety**: It parses values into their correct types (numbers, booleans, arrays) with safe fallbacks. - -### 2. RADIUS Client (`lib/radius.ts`) - -This module contains the low-level logic for communicating with a RADIUS server. - -- **Protocol**: It implements a minimal RADIUS client over UDP. -- **Packet Construction**: It builds `Access-Request` packets, properly encoding attributes like `User-Name` and `User-Password` (using PAP with MD5 hashing as per RFC 2865). -- **Response Parsing**: It parses `Access-Accept` and `Access-Reject` responses, and is capable of extracting the `Class` attribute (or other configured attributes) which is used for role and team mapping. -- **Security**: It includes logic to verify the response authenticator to protect against spoofed RADIUS replies. - -### 3. RADIUS Host Manager (`lib/radius_hosts.ts`) - -This is a critical component for ensuring the reliability of the authentication service. - -- **Host Pool**: It maintains an ordered list of RADIUS servers from the configuration (`RADIUS_HOSTS`). -- **Active Host Selection**: It maintains a single "active" host to which all authentication requests are sent. -- **Health Checks**: In the background, it periodically sends dummy `Access-Request` packets to all configured hosts to check their liveness. This is done via `setInterval`. -- **Automatic Failover**: If an authentication request to the active host times out, or if a background health check fails, the manager triggers a failover sequence. It iterates through the remaining hosts in priority order until a responsive one is found, which is then promoted to be the new active host. - -### 4. Storage Backend (`lib/storage.ts`) - -The proxy uses a simple, in-memory storage system for data that only needs to persist for a short duration. - -- **In-Memory**: It uses a global object (`global._oauth_codes`) to store authorization codes and refresh tokens. This avoids the need for an external database, simplifying deployment. -- **Data Stored**: - - **Authorization Codes**: Stores the code along with the user's details (username, groups, scope) and an expiry timestamp. - - **Refresh Tokens**: Stores refresh tokens with user details, allowing for persistent sessions. -- **Cleanup**: It includes a cleanup mechanism to periodically iterate through the stored data and remove expired entries, preventing memory leaks. - -### 5. JWT Manager (`lib/jwt.ts`) - -This module is responsible for all cryptographic operations related to JSON Web Tokens (JWTs). - -- **Key Generation**: On startup, it automatically generates a 2048-bit RSA key pair and saves it to the `.keys/` directory. This ensures that signed tokens remain valid across server restarts. -- **Algorithm Support**: It defaults to `RS256` for asymmetric signing. For simpler deployments or testing, it can be configured to use `HS256` with a shared secret. -- **Signing**: It creates and signs `id_token` and `access_token` JWTs with the appropriate claims (e.g., `sub`, `iss`, `aud`, `exp`, `email`, `groups`, `role`). -- **Verification**: It provides a function to verify the signature and validity of incoming access tokens, used by the `/userinfo` endpoint. - -### 6. Grafana API Client (`lib/grafana.ts`) - -This small utility handles communication with the Grafana API for team synchronization. - -- **API Calls**: It makes POST requests to Grafana's `/api/teams/{teamId}/members` endpoint. -- **User Lookup**: Before adding a user to a team, it first looks up the user's Grafana ID by their email address. It includes a retry mechanism to handle the delay between a user's first login and their account being provisioned in Grafana. -- **Idempotency**: It checks if a user is already a member of a team before attempting to add them, preventing redundant API calls. - -## API Endpoints (`app/api/`) - -The public-facing interface of the proxy is defined by a set of API routes. - -- `/api/oauth/authorize`: Handles both the initial GET request from Grafana and the POST request from the login form. -- `/api/oauth/token`: Exchanges authorization codes and refresh tokens for JWTs. -- `/api/oauth/userinfo`: Returns user claims from a valid access token. -- `/api/.well-known/openid-configuration`: The OIDC discovery endpoint. -- `/api/.well-known/jwks.json`: The OIDC JSON Web Key Set (JWKS) endpoint for public key distribution. - -## Frontend (`components/` and `app/page.tsx`) - -The user-facing part of the application is a simple Next.js page with a React Server Component (`page.tsx`) that renders a client-side login form (`login-form.tsx`). The form is styled using [shadcn/ui](https://ui.shadcn.com/) and [Tailwind CSS](https://tailwindcss.com/). diff --git a/docs/02-getting-started/01-installation.md b/docs/02-getting-started/01-installation.md deleted file mode 100644 index f9e41b8..0000000 --- a/docs/02-getting-started/01-installation.md +++ /dev/null @@ -1,130 +0,0 @@ -# Manual Installation - -This guide provides step-by-step instructions for setting up and running `radius-proxy` manually from the source code. This approach is suitable for development, testing, or production deployments where Docker is not used. - -## Prerequisites - -Before you begin, ensure you have the following software installed on your system: - -- **[Bun](https://bun.sh/)**: The application uses Bun as its JavaScript runtime and package manager. It is required for installing dependencies and running the application. -- **[Git](https://git-scm.com/)**: For cloning the source code repository. -- **A RADIUS Server**: You need access to a configured RADIUS server that the proxy can communicate with. - -## Step 1: Clone the Repository - -First, clone the `radius-proxy` repository to your local machine using Git. - -```bash -git clone https://github.com/essinghigh/radius-proxy.git -cd radius-proxy -``` - -## Step 2: Install Dependencies - -The project is located in the `radius-proxy/` subdirectory. Navigate into it and use Bun to install the required Node.js dependencies as defined in `package.json`. - -```bash -cd radius-proxy -bun install -``` - -This command will read the `package.json` file and install all necessary libraries (like Next.js, React, and jsonwebtoken) into the `node_modules` directory. - -## Step 3: Configure the Application - -The application is configured via a TOML file. A detailed example file, `config.example.toml`, is provided in the `radius-proxy/` directory. - -1. **Create a Configuration File**: - You can either modify `config.example.toml` directly or, for a cleaner setup, create your own `config.toml` file. The application will always prefer `config.toml` if it exists. - - ```bash - cp config.example.toml config.toml - ``` - -2. **Edit the Configuration**: - Open `config.toml` in a text editor and modify the parameters to match your environment. At a minimum, you must configure the following: - - - `RADIUS_HOSTS`: An array of your RADIUS server IP addresses or hostnames. - - `RADIUS_SECRET`: The shared secret for your RADIUS server(s). - - `OAUTH_CLIENT_ID`: The Client ID for the OAuth application (e.g., `grafana`). - - `OAUTH_CLIENT_SECRET`: The Client Secret for the OAuth application. - - `REDIRECT_URIS`: An array of allowed redirect URIs for your Grafana instance. This must exactly match the URI Grafana will use, e.g., `["https://grafana.yourcompany.com/login/generic_oauth"]`. - - `ISSUER`: The public-facing URL of the `radius-proxy` itself, e.g., `https://auth.yourcompany.com`. - - For a complete reference of all available parameters, see the **[Main Configuration](./../03-configuration/01-main-configuration.md)** guide. - - **Example `config.toml`:** - ```toml - # OAuth client credentials - OAUTH_CLIENT_ID = "grafana" - OAUTH_CLIENT_SECRET = "your-grafana-client-secret" - REDIRECT_URIS = ["https://grafana.example.com/login/generic_oauth"] - - # RADIUS servers - RADIUS_HOSTS = ["10.10.1.5", "10.10.1.6"] - RADIUS_SECRET = "your-radius-shared-secret" - - # Public URL of this proxy - ISSUER = "https://auth.example.com" - - # User configuration - EMAIL_SUFFIX = "example.com" - PERMITTED_CLASSES = "grafana-users,grafana-admins" - ADMIN_CLASSES = "grafana-admins" - ``` - -## Step 4: Run the Application - -Once the dependencies are installed and the configuration is in place, you can start the application. - -### For Development - -Use the `dev` script to run the Next.js application in development mode. This mode includes features like hot-reloading. - -```bash -bun run dev -``` - -By default, the server will start on port `54567`. You can access it at `http://localhost:54567`. - -### For Production - -For a production deployment, you should first build the optimized application and then start it. - -1. **Build the Application**: - This command compiles the Next.js application for production. - - ```bash - bun run build - ``` - -2. **Start the Server**: - This command runs the optimized production server. - - ```bash - bun run start - ``` - -The server will start on port `54567` by default. It is recommended to run the production server behind a reverse proxy like Nginx or Caddy to handle HTTPS, custom domains, and provide an additional layer of security. - -## Step 5: Configure Grafana - -Finally, configure Grafana to use `radius-proxy` as its OAuth 2.0 provider. In your `grafana.ini` file, add a section for Generic OAuth like the one below, adjusting the URLs and credentials to match your setup. - -```ini -[auth.generic_oauth] -enabled = true -name = RADIUS -allow_sign_up = true -client_id = your-grafana-client-secret -client_secret = your-grafana-client-secret -scopes = openid profile email -auth_url = https://auth.example.com/radius_login/api/oauth/authorize -token_url = https://auth.example.com/radius_login/api/oauth/token -api_url = https://auth.example.com/radius_login/api/oauth/userinfo -role_attribute_path = role == 'GrafanaAdmin' && 'Admin' || 'Viewer' -``` - -You can also configure this in Grafana's [SSO settings UI](https://grafana.com/whats-new/2024-02-26-sso-settings-ui-and-terraform-resource-for-configuring-oauth-providers/) - -After restarting Grafana, you should see a "Login with RADIUS" button on the login page. diff --git a/docs/02-getting-started/02-docker-setup.md b/docs/02-getting-started/02-docker-setup.md deleted file mode 100644 index 1117658..0000000 --- a/docs/02-getting-started/02-docker-setup.md +++ /dev/null @@ -1,127 +0,0 @@ -# Docker & Docker Compose Setup - -`radius-proxy` is designed to be easily containerized and deployed with Docker. This guide covers how to use the provided `Dockerfile` and `docker-compose.yml` to run the application in a containerized environment. - -## Prerequisites - -- **[Docker](https://docs.docker.com/get-docker/)**: The container runtime. -- **[Docker Compose](https://docs.docker.com/compose/install/)**: For easily managing the application container and its configuration. - -## Using the Pre-built Image with Docker Compose - -The easiest way to get started is by using the pre-built container image from the GitHub Container Registry (`ghcr.io`) along with the `docker-compose.yml` file at the root of the repository. - -### Step 1: Create a Configuration File - -The Docker Compose setup is designed to mount a local configuration file into the container. You must create this file before starting the service. - -1. **Navigate to the repository root**: - - ```bash - cd /path/to/radius-proxy - ``` - -2. **Create `config.toml`**: - Create a file named `config.toml` in the root directory. This file will be mounted into the container at `/app/config.toml`. - - You can copy the example configuration from `radius-proxy/config.example.toml` as a starting point. - - ```bash - cp radius-proxy/config.example.toml ./config.toml - ``` - -3. **Edit `config.toml`**: - Modify `./config.toml` with your specific settings, such as RADIUS server details, OAuth credentials, and the `ISSUER` URL. - - **Important**: The `HTTP_HOST` in your `config.toml` should be set to `0.0.0.0` to allow the Next.js server inside the container to accept connections from the Docker network. The `HTTP_PORT` should match the internal port used by the application (default `54567`). - - ```toml - # ./config.toml - - HTTP_HOST = "0.0.0.0" - HTTP_PORT = 54567 - - ISSUER = "http://auth.example.local:54567" - - RADIUS_HOSTS = ["10.10.1.5"] - RADIUS_SECRET = "your-radius-secret" - - # ... other settings - ``` - -### Step 2: Customize `docker-compose.yml` - -The provided `docker-compose.yml` file uses environment variables to configure the container. You can set these directly in your shell or create a `.env` file in the same directory. - -**`docker-compose.yml`:** -```yaml -version: '3.8' - -services: - radius-proxy: - image: ghcr.io/essinghigh/radius-proxy:latest - restart: unless-stopped - ports: - - "${HOST_PORT:-54567}:54567" - environment: - - NODE_ENV=production - - ISSUER=http://auth.example.local:${HOST_PORT:-54567} - # Add other environment variable overrides here if needed - volumes: - - type: bind - source: ./config.toml - target: /app/config.toml - read_only: true -``` - -- `HOST_PORT`: This variable determines the port on the host machine that will map to the container's port `54567`. If not set, it defaults to `54567`. -- `ISSUER`: This should be set to the public URL of the proxy. It's important that this matches the `ISSUER` in your `config.toml` and is accessible by Grafana. - -### Step 3: Run the Container - -With your `config.toml` and `docker-compose.yml` in place, you can start the container. - -```bash -# To run with a custom host port -export HOST_PORT=8080 -docker compose up -d - -# To run with the default port (54567) -docker compose up -d -``` - -The `-d` flag runs the container in detached mode. You can view the logs using: - -```bash -docker compose logs -f -``` - -## Building the Docker Image Manually - -If you need to build the image from source (for example, after making code changes), you can use the `Dockerfile` located in the `radius-proxy/` directory. - -The `Dockerfile` uses a multi-stage build process: - -1. **`builder` stage**: Installs all dependencies (including `devDependencies`), and runs `bun run build` to create an optimized Next.js production build. -2. **`runner` stage**: A smaller final image that installs only production dependencies and copies the build artifacts from the `builder` stage. This results in a lean and secure final image. - -### Build Command - -To build the image, run the following command from the root of the repository: - -```bash -docker build -t my-radius-proxy:latest -f radius-proxy/Dockerfile . -``` - -- `-t my-radius-proxy:latest`: Tags the built image with a name and tag of your choice. -- `-f radius-proxy/Dockerfile`: Specifies the path to the Dockerfile. -- `.`: Sets the build context to the root of the repository. - -After building, you can update your `docker-compose.yml` to use your custom image: - -```yaml -services: - radius-proxy: - image: my-radius-proxy:latest - # ... rest of the configuration -``` diff --git a/docs/03-configuration/01-main-configuration.md b/docs/03-configuration/01-main-configuration.md deleted file mode 100644 index b917cd5..0000000 --- a/docs/03-configuration/01-main-configuration.md +++ /dev/null @@ -1,202 +0,0 @@ -# Main Configuration (`config.toml`) - -`radius-proxy` is configured primarily through a TOML file. The application looks for `config.toml` in the working directory at startup. If it's not found, it falls back to `config.example.toml`. - -This document provides a comprehensive reference for every available parameter. - ---- - -## [OAuth 2.0 / OIDC] - -These settings define the behavior of the proxy as an OAuth 2.0 and OpenID Connect provider. - -### `OAUTH_CLIENT_ID` -- **Type**: `String` -- **Default**: `"grafana"` -- **Description**: The client ID that Grafana (or any other OAuth client) must use to identify itself. This must match the `client_id` configured in Grafana's Generic OAuth settings. - -### `OAUTH_CLIENT_SECRET` -- **Type**: `String` -- **Default**: `"secret"` -- **Description**: The client secret that Grafana must use to authenticate itself during the token exchange. This must match the `client_secret` configured in Grafana. - -### `OAUTH_CODE_TTL` -- **Type**: `Number` -- **Default**: `600` -- **Description**: The Time-To-Live (TTL) for authorization codes, in seconds. An authorization code is issued after a successful login and must be exchanged for a token within this timeframe. A longer TTL may be convenient but increases the risk if a code is intercepted. - -### `OAUTH_REFRESH_TOKEN_TTL` -- **Type**: `Number` -- **Default**: `7776000` (90 days) -- **Description**: The Time-To-Live (TTL) for refresh tokens, in seconds. Refresh tokens allow Grafana to obtain new access tokens without requiring the user to log in again. - -### `REDIRECT_URIS` -- **Type**: `Array of Strings` -- **Default**: `[]` -- **Description**: A list of allowed redirect URIs. When Grafana initiates a login, it provides a `redirect_uri` where the user should be sent back after authentication. To prevent open redirect vulnerabilities, the proxy will only redirect to URIs that are on this allowlist. The URI must be an exact match, including the path. -- **Example**: `REDIRECT_URIS = ["https://grafana.mycompany.com/login/generic_oauth"]` - -### `ISSUER` -- **Type**: `String` -- **Default**: (derived from request headers) -- **Description**: The canonical, public-facing base URL of the `radius-proxy` service. This URL is used to construct the endpoints in the OIDC discovery document (e.g., `authorization_endpoint`, `token_endpoint`). It should be the URL that Grafana and end-users can reach. If not set, the proxy attempts to derive it from `X-Forwarded-*` or `Host` headers. -- **Example**: `ISSUER = "https://auth.mycompany.com"` - ---- - -### Scope Validation - -`radius-proxy` enforces an allowlist of supported scopes. The current supported scopes are: - -- `openid` -- `profile` -- `email` - -Behavior: - -- If no `scope` parameter is provided, the default scopes `openid profile` are applied. -- Duplicate scopes and casing differences are normalized (e.g. `OPENID profile openid` becomes `openid profile`). -- Any unsupported scope token results in an `invalid_scope` error (redirect or JSON depending on request mode). -- Refresh token exchanges reuse the originally granted scope; scope cannot be expanded during refresh. - -To add new scopes in the future, update `lib/scopes.ts` (`SUPPORTED_SCOPES` and `DEFAULT_SCOPES`). The discovery document will automatically reflect the new values. - ---- - -## [RADIUS] - -These settings configure the connection to your RADIUS servers. - -### `RADIUS_HOSTS` -- **Type**: `Array of Strings` -- **Default**: `["127.0.0.1"]` -- **Description**: An ordered list of RADIUS server IP addresses or hostnames. The proxy will always try the first server in the list. If it becomes unresponsive, it will fail over to the next one in the list. See the [RADIUS Failover](./../04-features/02-radius-failover.md) documentation for more details. -- **Example**: `RADIUS_HOSTS = ["10.0.0.1", "10.0.0.2", "10.0.0.3"]` - -### `RADIUS_SECRET` -- **Type**: `String` -- **Default**: `"secret"` -- **Description**: The shared secret used to encrypt and validate communication with all configured RADIUS servers. - -### `RADIUS_PORT` -- **Type**: `Number` -- **Default**: `1812` -- **Description**: The UDP port on which the RADIUS servers are listening for authentication requests. - -### `RADIUS_TIMEOUT` -- **Type**: `Number` -- **Default**: `5` -- **Description**: The timeout in seconds for a single RADIUS authentication request. If a RADIUS server does not reply within this time, the request is considered failed, which may trigger a failover. - -### `RADIUS_ASSIGNMENT` -- **Type**: `Number` -- **Default**: `25` -- **Description**: The numeric code of the RADIUS attribute that contains the user's group or role information. This is used for mapping to Grafana roles and teams. The default `25` corresponds to the `Class` attribute. -- **Examples**: `25` (Class), `11` (Filter-Id), `26` (Vendor-Specific). - -### Vendor-Specific Attribute (VSA) Settings - -These settings are used only when `RADIUS_ASSIGNMENT` is set to `26`. - -- **`RADIUS_VENDOR_ID`**: The vendor ID for the VSA. -- **`RADIUS_VENDOR_TYPE`**: The vendor-specific attribute type number. -- **`RADIUS_VALUE_PATTERN`**: A regular expression with a capture group to extract the desired value from the VSA string. For example, for a Cisco AVPair like `shell:roles=admin`, you could use `"shell:roles=([^,s]+)"` to extract `admin`. - ---- - -## [RADIUS Health Checks] - -Settings for the high-availability and failover mechanism. - -### `RADIUS_HEALTHCHECK_INTERVAL` -- **Type**: `Number` -- **Default**: `1800` (30 minutes) -- **Description**: The interval in seconds at which the proxy will perform background health checks on all configured RADIUS servers to monitor their liveness. - -### `RADIUS_HEALTHCHECK_TIMEOUT` -- **Type**: `Number` -- **Default**: `5` -- **Description**: The timeout in seconds for a single health check probe. This is separate from the main `RADIUS_TIMEOUT`. - -### `RADIUS_HEALTHCHECK_USER` -- **Type**: `String` -- **Default**: `"grafana_dummy_user"` -- **Description**: A dummy username to use for sending health check `Access-Request` packets. - -### `RADIUS_HEALTHCHECK_PASSWORD` -- **Type**: `String` -- **Default**: `"dummy_password"` -- **Description**: The password for the dummy health check user. - ---- - -## [Server] - -Configuration for the built-in HTTP server. - -### `HTTP_HOST` -- **Type**: `String` -- **Default**: `"0.0.0.0"` -- **Description**: The network interface on which the server will listen. `0.0.0.0` means it will listen on all available interfaces, which is required for Docker deployments. - -### `HTTP_PORT` -- **Type**: `Number` -- **Default**: `54567` -- **Description**: The TCP port on which the server will listen. - ---- - -## [User & Group Management] - -Settings related to user identity and permissions. - -### `EMAIL_SUFFIX` -- **Type**: `String` -- **Default**: `"example.local"` -- **Description**: The email domain to append to a user's RADIUS username to form their email address claim in the JWT. For example, if a user logs in as `jdoe` and the suffix is `mycompany.com`, their email claim will be `jdoe@mycompany.com`. - -### `PERMITTED_CLASSES` -- **Type**: `String` (comma-separated) -- **Default**: `""` (empty string, meaning all are permitted) -- **Description**: A comma-separated list of RADIUS `Class` attribute values that are allowed to log in. If a user authenticates successfully but their `Class` attribute is not in this list, their login will be rejected. If this list is empty, all users who successfully authenticate are permitted. -- **Example**: `PERMITTED_CLASSES = "vpn-users,grafana-users,admins"` - -### `ADMIN_CLASSES` -- **Type**: `String` (comma-separated) -- **Default**: `""` -- **Description**: A comma-separated list of RADIUS `Class` attribute values that should be granted Grafana Admin privileges. If a user's `Class` is in this list, their JWT will include the claim `"role": "GrafanaAdmin"`. -- **Example**: `ADMIN_CLASSES = "grafana-admins,superusers"` - ---- - -## [Grafana Team Synchronization] - -Optional settings for automatically managing Grafana team memberships. - -### `GRAFANA_BASE_URL` -- **Type**: `String` -- **Default**: `""` -- **Description**: The base URL of your Grafana instance. This is required for the proxy to make API calls to Grafana. -- **Example**: `GRAFANA_BASE_URL = "https://grafana.mycompany.com"` - -### `GRAFANA_SA_TOKEN` -- **Type**: `String` -- **Default**: `""` -- **Description**: A Grafana Service Account token with `teams.write` and `users.read` permissions. This is required for the proxy to add users to teams. - -### `GRAFANA_INSECURE_TLS` -- **Type**: `Boolean` -- **Default**: `false` -- **Description**: If `true`, TLS certificate verification will be disabled for API calls made to the Grafana server. This is useful for development environments or when Grafana is using a self-signed certificate, but it is insecure and should not be used in production unless you understand the risks. - -### `CLASS_MAP` -- **Type**: `Table` / `Map` -- **Default**: `{}` -- **Description**: A map where keys are RADIUS `Class` attribute values and values are arrays of Grafana Team IDs. When a user logs in, the proxy will check their `Class` attribute and, if it matches a key in this map, will attempt to add the user to the corresponding Grafana teams. -- **Example**: - ```toml - [CLASS_MAP] - finance-team = [1, 5] - engineering-team = [2, 8] - ``` - In this example, a user with the `finance-team` class will be added to Grafana teams with IDs 1 and 5. diff --git a/docs/03-configuration/02-environment-variables.md b/docs/03-configuration/02-environment-variables.md deleted file mode 100644 index 61bc23f..0000000 --- a/docs/03-configuration/02-environment-variables.md +++ /dev/null @@ -1,83 +0,0 @@ -# Environment Variable Configuration - -While `radius-proxy` is primarily configured via the `config.toml` file, every parameter can be overridden using environment variables. This is the recommended approach for configuring the application in containerized environments like Docker, as it avoids the need to bake secrets into configuration files. - -## Precedence - -Environment variables **always take precedence** over values defined in `config.toml`. If an environment variable is set, its value will be used, regardless of what is in the configuration file. - -## Naming Convention - -The environment variables map directly to the keys in the `config.toml` file. The convention is to use the uppercase version of the configuration key. - -For example: -- The `OAUTH_CLIENT_ID` key in `config.toml` is overridden by the `OAUTH_CLIENT_ID` environment variable. -- The `RADIUS_HOSTS` key is overridden by the `RADIUS_HOSTS` environment variable. - -## Variable Reference - -Here is a complete list of environment variables that can be used to configure the application. - -### OAuth 2.0 / OIDC - -| Environment Variable | `config.toml` Key | Description | -| ------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `OAUTH_CLIENT_ID` | `OAUTH_CLIENT_ID` | The OAuth client ID. | -| `OAUTH_CLIENT_SECRET` | `OAUTH_CLIENT_SECRET` | The OAuth client secret. | -| `OAUTH_CODE_TTL` | `OAUTH_CODE_TTL` | The Time-To-Live for authorization codes, in seconds. | -| `OAUTH_REFRESH_TOKEN_TTL` | `OAUTH_REFRESH_TOKEN_TTL` | The Time-To-Live for refresh tokens, in seconds. | -| `REDIRECT_URIS` | `REDIRECT_URIS` | A **comma-separated** string of allowed redirect URIs. Example: `"https://grafana.com/login,https://grafana2.com/login"` | -| `ISSUER` | `ISSUER` | The canonical public-facing base URL of the proxy. | - -### RADIUS - -| Environment Variable | `config.toml` Key | Description - | -| `RADIUS_HOSTS` | `RADIUS_HOSTS` | A **comma-separated or space-separated** string of RADIUS server hostnames or IPs. Example: `"10.0.0.1,10.0.0.2"` - | -| `RADIUS_SECRET` | `RADIUS_SECRET` | The shared secret for the RADIUS servers. - | -| `RADIUS_PORT` | `RADIUS_PORT` | The UDP port for RADIUS authentication requests. - | -| `RADIUS_TIMEOUT` | `RADIUS_TIMEOUT` | The timeout in seconds for a RADIUS authentication request. - | -| `RADIUS_ASSIGNMENT` | `RADIUS_ASSIGNMENT` | The numeric code of the RADIUS attribute to use for group/role assignment. - | - -### RADIUS Health Checks - -| Environment Variable | `config.toml` Key | Description - | -| ------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `RADIUS_HEALTHCHECK_INTERVAL` | `RADIUS_HEALTHCHECK_INTERVAL` | The interval in seconds for background RADIUS health checks. - | -| `RADIUS_HEALTHCHECK_TIMEOUT` | `RADIUS_HEALTHCHECK_TIMEOUT` | The timeout in seconds for a single health check probe. - | -| `RADIUS_HEALTHCHECK_USER` | `RADIUS_HEALTHCHECK_USER` | The dummy username for health check probes. - | -'| -| `RADIUS_HEALTHCHECK_PASSWORD` | `RADIUS_HEALTHCHECK_PASSWORD` | The password for the dummy health check user. - | - -### Server - -| Environment Variable | `config.toml` Key | Description - | -| ------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `HTTP_HOST` | `HTTP_HOST` | The network interface on which the server will listen (e.g., `0.0.0.0`). - | -| `HTTP_PORT` | `HTTP_PORT` | The TCP port on which the server will listen. - | - -### User & Group Management - -| Environment Variable | `config.toml` Key | Description - | -| ------------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `EMAIL_SUFFIX` | `EMAIL_SUFFIX` | The email domain to append to usernames. - | -| `PERMITTED_CLASSES` | `PERMITTED_CLASSES` | A **comma-separated** string of RADIUS `Class` values that are allowed to log in. - | -| `ADMIN_CLASSES` | `ADMIN_CLASSES` | A **comma-separated** string of RADIUS `Class` values that grant Grafana Admin privileges. - | - -### Grafana Team Synchronization - -| Environment Variable | `config.toml` Key | Description - | -| ------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `GRAFANA_BASE_URL` | `GRAFANA_BASE_URL` | The base URL of your Grafana instance. - | -| `GRAFANA_SA_TOKEN` | `GRAFANA_SA_TOKEN` | A Grafana Service Account token with `teams.write` and `users.read` permissions. - | -| `GRAFANA_INSECURE_TLS` | `GRAFANA_INSECURE_TLS` | Set to `true` to disable TLS certificate verification for Grafana API calls. **Use with caution.** - | -| `CLASS_MAP` | `CLASS_MAP` | A **JSON string** representing the mapping of RADIUS classes to Grafana team IDs. Example: `'{"team-a":[1,2],"team-b":[3]}'` | - -### JWT Signing - -These variables control how JWTs are signed. You can use an RSA key pair or a simpler HS256 secret. - -| Environment Variable | `config.toml` Key | Description - | -| -------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_PRIVATE_KEY` | N/A | A string containing the private RSA key in PEM format. If set, `RS256` will be used. - | -| `JWT_PUBLIC_KEY` | N/A | A string containing the public RSA key in PEM format. - | -| `JWT_HS256_SECRET` | N/A | A secret string for signing tokens with `HS256`. If this is set, it will be used instead of RSA keys. - | diff --git a/docs/04-features/01-authentication-flow.md b/docs/04-features/01-authentication-flow.md deleted file mode 100644 index a838f8e..0000000 --- a/docs/04-features/01-authentication-flow.md +++ /dev/null @@ -1,149 +0,0 @@ -# Authentication Flow - -This document provides a detailed, step-by-step breakdown of the complete authentication flow, from the moment a user initiates a login in Grafana to the final token validation and session creation. - -## Actors - -- **User**: The person trying to log into Grafana. -- **Grafana**: The client application that is configured to use `radius-proxy` for authentication. -- **Radius-Proxy**: This application, acting as the OAuth 2.0 and OIDC provider. -- **RADIUS Server**: The external authentication authority. - -## Sequence Diagram - -```mermaid -sequenceDiagram - participant User - participant Grafana - participant RadiusProxy as Radius-Proxy - participant RadiusServer as RADIUS Server - - User->>Grafana: Clicks "Login with RADIUS" - Grafana->>User: Redirect to /authorize endpoint - - User->>RadiusProxy: GET /api/oauth/authorize?client_id=...&redirect_uri=... - Note over RadiusProxy: Validates client_id, redirect_uri, response_type. - RadiusProxy->>User: Redirect to /radius_login (Login Page UI) - - User->>RadiusProxy: Fills and submits login form (POST /api/oauth/authorize) - Note over RadiusProxy: Receives username, password, client_id, etc. - - RadiusProxy->>RadiusServer: Sends RADIUS Access-Request - Note over RadiusProxy,RadiusServer: Uses PAP to authenticate user. - RadiusServer-->>RadiusProxy: Responds with RADIUS Access-Accept (with Class attribute) - - Note over RadiusProxy: Verifies user is permitted (PERMITTED_CLASSES). - RadiusProxy->>RadiusProxy: Generates one-time authorization code. - RadiusProxy->>RadiusProxy: Stores code with user details (username, groups, expiry). - RadiusProxy->>User: Redirect to Grafana's redirect_uri with code. - - User->>Grafana: Follows redirect: /login/generic_oauth?code=... - - Grafana->>RadiusProxy: Back-channel: POST /api/oauth/token (sends code, client_id, client_secret) - Note over RadiusProxy: Validates client credentials and authorization code. - Note over RadiusProxy: Verifies PKCE challenge if present. - RadiusProxy->>RadiusProxy: Deletes used authorization code. - RadiusProxy->>RadiusProxy: Generates and signs id_token and access_token (JWTs). - RadiusProxy->>RadiusProxy: Generates and stores refresh_token. - RadiusProxy-->>Grafana: Returns JSON with access_token, id_token, refresh_token. - - Grafana->>Grafana: Validates JWTs and extracts claims (sub, email, role). - Note over Grafana: Creates user session. - Grafana->>User: Renders Grafana dashboard (logged in). - - %% Optional Team Sync - alt After token issuance - RadiusProxy->>Grafana: (Async) API call to add user to teams based on CLASS_MAP. - end -``` - -## Detailed Steps - -### Part 1: Authorization Request - -1. **Initiation (Grafana -> Proxy)** - - The user clicks the login button in Grafana. - - Grafana constructs an OAuth 2.0 authorization URL and redirects the user's browser to it. - - This is a `GET` request to the proxy's `/api/oauth/authorize` endpoint. - - **Code Reference**: `app/api/oauth/authorize/route.ts` (`GET` handler) - - **Parameters included**: `client_id`, `redirect_uri`, `response_type=code`, `state`, and potentially `code_challenge` and `code_challenge_method` for PKCE. - -2. **Request Validation and UI Redirect (Proxy)** - - The proxy's `GET` handler for `/api/oauth/authorize` receives the request. - - It performs initial validation: - - Ensures `client_id`, `redirect_uri`, and `response_type` are present. - - Validates that the `client_id` matches the one in its configuration (`OAUTH_CLIENT_ID`). - - If valid, it does **not** immediately authenticate. Instead, it redirects the user to its own user-facing login page (`/radius_login`), preserving all the original query parameters (`client_id`, `state`, etc.). - -### Part 2: User Authentication - -3. **Credential Submission (User -> Proxy)** - - The user sees the login form rendered by `app/page.tsx` and `components/login-form.tsx`. - - The user enters their RADIUS username and password and submits the form. - - This action sends a `POST` request back to the `/api/oauth/authorize` endpoint. - - **Code Reference**: `app/api/oauth/authorize/route.ts` (`POST` handler) - - The form body contains the `user`, `password`, and the original OAuth parameters (`client_id`, `redirect_uri`, `state`). - -4. **RADIUS Authentication (Proxy -> RADIUS Server)** - - The `POST` handler extracts the credentials. - - It retrieves the currently active RADIUS server from the `RadiusHostManager` (`lib/radius_hosts.ts`). - - It calls `radiusAuthenticate` from `lib/radius.ts`, which constructs a RADIUS `Access-Request` packet. - - The user's password is encrypted using the RADIUS PAP mechanism (MD5 hash of the shared secret and the request authenticator). - - The packet is sent over UDP to the RADIUS server. - -5. **RADIUS Response Handling (Proxy)** - - The proxy waits for a response from the RADIUS server (with a configurable timeout). - - If the credentials are valid, the server returns an `Access-Accept` packet. If not, it returns `Access-Reject`. - - The proxy parses the response. If successful, it extracts the `Class` attribute (or the attribute configured via `RADIUS_ASSIGNMENT`). - -### Part 3: Authorization Code Issuance - -6. **Permissions Check and Code Generation (Proxy)** - - The proxy checks if the user's `Class` attribute is in the `PERMITTED_CLASSES` list (if configured). If not, the flow is terminated with an `access_denied` error. - - If permitted, it generates a cryptographically secure, random string to be used as the **authorization code**. - - It stores this code in the in-memory storage (`lib/storage.ts`) along with the user's identity (`username`, `groups`), the requested `scope`, and an expiration timestamp (`OAUTH_CODE_TTL`). If PKCE was used, the `code_challenge` is also stored. - -7. **Redirect to Grafana (Proxy -> User -> Grafana)** - - The proxy constructs the final redirect URL using the `redirect_uri` provided by Grafana. - - Before redirecting, it performs a strict validation on the `redirect_uri` against the `REDIRECT_URIS` allowlist to prevent open redirect attacks. - - It appends the newly generated `code` and the original `state` parameter to the URL. - - It sends an HTTP `302 Found` redirect response to the user's browser. - - The browser follows this redirect, sending the authorization code to Grafana's callback endpoint. - -### Part 4: Token Exchange - -8. **Code for Token Exchange (Grafana -> Proxy)** - - Grafana receives the authorization code at its callback endpoint. - - It then makes a direct, back-channel `POST` request to the proxy's `/api/oauth/token` endpoint. - - **Code Reference**: `app/api/oauth/token/route.ts` (`POST` handler) - - This request includes the `grant_type=authorization_code`, the `code`, its `client_id`, and its `client_secret`. If using PKCE, it also includes the `code_verifier`. - -9. **Code Validation and Token Generation (Proxy)** - - The `/token` endpoint first authenticates the client by checking its `client_id` and `client_secret`. - - It looks up the provided `code` in the in-memory storage. - - It performs several checks: - - Does the code exist? - - Has the code expired? - - **PKCE Verification**: If a `code_challenge` was stored with the code, it verifies the `code_verifier` from the request against the challenge. - - If all checks pass, it **deletes the authorization code** from storage to ensure it can only be used once. - -10. **JWT Issuance (Proxy -> Grafana)** - - The proxy generates a set of JSON Web Tokens (JWTs) using the `lib/jwt.ts` module: - - **`id_token`**: An OIDC token containing claims about the user's identity, such as `sub` (username), `name`, `email`, `groups`, and `role` (if applicable, from `ADMIN_CLASSES`). - - **`access_token`**: An OAuth 2.0 token that can be used to access the `/userinfo` endpoint. - - **`refresh_token`**: A long-lived token that can be used to get new access tokens. - - It sends these tokens back to Grafana in a JSON response. - -### Part 5: Session Creation and Team Sync - -11. **Login Completion (Grafana)** - - Grafana receives the tokens. - - It validates the signature and claims of the `id_token` (checking the `iss` and `aud` claims). - - It uses the claims within the token to provision a user session, mapping the `role` claim to a Grafana organization role. - - The user is now logged into Grafana. - -12. **Team Synchronization (Proxy -> Grafana, Optional)** - - Asynchronously, after issuing the tokens, the `radius-proxy` can perform an extra step if `GRAFANA_SA_TOKEN` and `CLASS_MAP` are configured. - - It uses the user's group information and the `CLASS_MAP` to determine which Grafana teams the user should belong to. - - Using the `GRAFANA_SA_TOKEN`, it calls the Grafana API to add the user to the appropriate teams. - - This process is best-effort and does not block or fail the authentication flow. diff --git a/docs/04-features/02-radius-failover.md b/docs/04-features/02-radius-failover.md deleted file mode 100644 index 76973d4..0000000 --- a/docs/04-features/02-radius-failover.md +++ /dev/null @@ -1,74 +0,0 @@ -# RADIUS Failover & Health Checks - -To ensure high availability, `radius-proxy` implements a robust RADIUS host management system that includes automatic failover and periodic health checks. This system guarantees that user authentications can continue even if the primary RADIUS server becomes unavailable. - -This entire mechanism is managed by the `RadiusHostManager` class, located in `lib/radius_hosts.ts`. - -## Core Concepts - -### Host Pool - -The foundation of the system is the **host pool**, an ordered list of RADIUS server IP addresses or hostnames defined in the `config.toml` file under the `RADIUS_HOSTS` key. - -```toml -# The order defines the priority: 10.0.0.1 is tried first. -RADIUS_HOSTS = ["10.0.0.1", "10.0.0.2", "10.0.0.3"] -``` - -The order of hosts in this array is critical, as it defines the failover priority. - -### Active Host - -At any given time, the `RadiusHostManager` maintains a single **active host**. All incoming authentication requests are sent exclusively to this host. This prevents scattering requests across multiple servers and provides a predictable routing pattern. - -The initial active host is determined at startup by probing each host in the configured order until a responsive one is found. - -### Health State - -For each host in the pool, the manager tracks its health state, which includes: -- `lastOkAt`: The timestamp of the last successful communication. -- `lastTriedAt`: The timestamp of the last attempted communication. -- `consecutiveFailures`: The number of consecutive failed probes. - -## Health Check Mechanism - -To proactively monitor the status of all RADIUS servers, the manager runs a background health check cycle. - -- **Trigger**: The health check cycle is triggered by a `setInterval` loop. -- **Interval**: The frequency of these checks is controlled by the `RADIUS_HEALTHCHECK_INTERVAL` parameter (default: 1800 seconds / 30 minutes). -- **Probe**: A health check consists of sending a RADIUS `Access-Request` packet to a host using a dummy username and password (configured via `RADIUS_HEALTHCHECK_USER` and `RADIUS_HEALTHCHECK_PASSWORD`). -- **Success Criteria**: A probe is considered successful if the host responds at all. This includes both `Access-Accept` and `Access-Reject` responses. The goal is to check for liveness (the server is running and reachable), not to validate the dummy credentials. -- **Timeout**: Each probe has its own timeout, defined by `RADIUS_HEALTHCHECK_TIMEOUT` (default: 5 seconds). A timeout is considered a failure. - -## Automatic Failover - -Failover is the process of automatically switching the active host when the current one is determined to be down. This can be triggered in two ways: - -1. **Reactive Failover (During User Authentication)** - - A user attempts to log in, and the proxy sends an `Access-Request` to the current active host. - - The request times out (as defined by `RADIUS_TIMEOUT`). - - The `radiusAuthenticate` function catches the timeout and notifies the `RadiusHostManager`. - - The manager immediately initiates a **failover sequence**. - -2. **Proactive Failover (During Health Check)** - - The background health check cycle runs. - - It probes the current active host, and the probe fails (e.g., times out). - - The manager immediately initiates a **failover sequence**. - -### The Failover Sequence - -When a failover is triggered, the `RadiusHostManager` performs the following steps: - -1. It marks the current active host as potentially down. -2. It creates a new priority list of hosts to try, starting with the one immediately following the failed host in the original `RADIUS_HOSTS` array and wrapping around. - - *Example*: If `RADIUS_HOSTS` is `[A, B, C]` and `A` fails, the failover order will be `B`, then `C`. -3. It probes the next host in the failover list. -4. If the host responds, it is immediately promoted to be the new **active host**, and the failover sequence stops. -5. If the host does not respond, the manager moves to the next host in the sequence and repeats the probe. -6. If the manager cycles through all other hosts and none of them respond, it will clear the active host. In this state, the next user authentication attempt will trigger a new search for a responsive host from the beginning of the `RADIUS_HOSTS` list. - -This ensures that the system can automatically recover from a RADIUS server failure with minimal disruption to users. - -## Startup Behavior - -On application startup, the `RadiusHostManager` immediately runs a `fastFailoverSequence` to find the first available host and set it as active. This ensures that the proxy is ready to serve authentication requests as quickly as possible, without having to wait for the first user login to discover a working server. diff --git a/docs/04-features/03-grafana-integration.md b/docs/04-features/03-grafana-integration.md deleted file mode 100644 index 7adacd2..0000000 --- a/docs/04-features/03-grafana-integration.md +++ /dev/null @@ -1,98 +0,0 @@ -# Grafana Integration - -`radius-proxy` is purpose-built to integrate seamlessly with Grafana's "Generic OAuth" authentication option. This integration goes beyond simple authentication and includes advanced features like role mapping and automatic team synchronization. - -## JWT Claims for Grafana - -When Grafana receives an `id_token` from the proxy, it uses the claims within the JWT payload to provision the user's session. `radius-proxy` generates a specific set of claims tailored for Grafana's consumption. - -**Code Reference**: The claims are assembled in `app/api/oauth/token/route.ts`. - -An example JWT payload might look like this: - -```json -{ - "sub": "jdoe", - "name": "jdoe", - "email": "jdoe@example.local", - "groups": ["finance-team", "vpn-users"], - "role": "GrafanaAdmin", - "iss": "https://auth.mycompany.com", - "aud": "grafana", - "iat": 1678886400, - "exp": 1678890000 -} -``` - -Key claims used by Grafana: - -- `sub` (Subject): This is used as the user's unique identifier. It is set to the RADIUS username. -- `name`: The display name for the user in Grafana. Also set to the RADIUS username. -- `email`: The user's email address. This is synthesized by combining the username (`sub`) with the `EMAIL_SUFFIX` from the configuration (e.g., `jdoe` + `@example.local` = `jdoe@example.local`). -- `groups`: An array of strings representing the user's group memberships. This is derived from the RADIUS `Class` attribute (or another attribute specified by `RADIUS_ASSIGNMENT`). If the `Class` attribute contains delimiters (`;` or `,`), it is split into multiple groups. -- `role`: This claim is used to determine the user's organization role in Grafana. Its value is determined by the `ADMIN_CLASSES` configuration. - -## Role Mapping - -`radius-proxy` can dynamically assign a user's role in Grafana (Viewer, Editor, or Admin) based on their RADIUS group. - -This is controlled by the `ADMIN_CLASSES` parameter in `config.toml`. - -- **Mechanism**: When a user logs in, the proxy checks if any of the user's groups (from the `Class` attribute) are present in the `ADMIN_CLASSES` list. -- **Behavior**: If there is a match, the proxy includes the claim `"role": "GrafanaAdmin"` in the JWT. -- **Grafana Configuration**: To make this work, you must configure Grafana's `role_attribute_path` to interpret this claim. A common JMESPath expression for this is: - - ```ini - # In grafana.ini - [auth.generic_oauth] - role_attribute_path = "contains(groups, 'grafana-admins') && 'Admin' || 'Viewer'" - ``` - Or, if using the `role` claim directly: - ```ini - role_attribute_path = "role == 'GrafanaAdmin' && 'Admin' || 'Viewer'" - ``` - -This allows you to manage Grafana administrative privileges directly from your RADIUS server. - -## Automatic Team Synchronization - -A powerful feature of the proxy is its ability to automatically manage a user's team memberships in Grafana. - -**Code Reference**: `lib/grafana.ts` and the asynchronous block in `app/api/oauth/token/route.ts`. - -### How It Works - -1. **Configuration**: This feature is enabled by configuring the `GRAFANA_BASE_URL`, `GRAFANA_SA_TOKEN`, and `CLASS_MAP` parameters in `config.toml`. - - ```toml - # URL of your Grafana instance - GRAFANA_BASE_URL = "https://grafana.mycompany.com" - - # A Grafana Service Account token with appropriate permissions - GRAFANA_SA_TOKEN = "glsa_..." - - # Mapping of RADIUS groups to Grafana Team IDs - [CLASS_MAP] - finance-team = [1, 5] - engineering-team = [2] - ``` - -2. **Trigger**: After a user successfully authenticates and a token is issued, the proxy triggers an asynchronous (non-blocking) process. - -3. **User Lookup**: The proxy first needs to find the user's numeric `userId` in Grafana. It does this by making an API call to Grafana's `/api/org/users/lookup` endpoint using the user's email address. - - **Retry Logic**: Because a user might be logging in for the first time, their account may not be fully provisioned in Grafana when the token is issued. The proxy includes a retry mechanism with exponential backoff to handle this potential race condition, polling the lookup endpoint a few times before giving up. - -4. **Team Membership Check**: Before adding a user to a team, the proxy first fetches the team's current member list to see if the user is already a member. This makes the process idempotent and avoids unnecessary API calls. - -5. **Add to Team**: If the user is not already a member, the proxy makes a `POST` request to Grafana's `/api/teams/{teamId}/members` endpoint to add the user to the team. - -### Permissions - -For this feature to work, the Grafana Service Account token provided in `GRAFANA_SA_TOKEN` must have the following permissions: - -- **Users**: `users:read` (to look up users by email). -- **Teams**: `teams:read` and `teams.write` (to check and add team members). - -### Insecure TLS - -For development or internal environments where Grafana might be using a self-signed TLS certificate, you can set `GRAFANA_INSECURE_TLS = true`. This will temporarily disable TLS certificate validation for the API calls made from the proxy to Grafana. **This is insecure and should not be used in a production environment exposed to the internet.** diff --git a/docs/04-features/04-security.md b/docs/04-features/04-security.md deleted file mode 100644 index e0c4f4f..0000000 --- a/docs/04-features/04-security.md +++ /dev/null @@ -1,70 +0,0 @@ -# Security - -`radius-proxy` is designed with security as a primary consideration. This document outlines the key security measures implemented to protect the authentication flow, the application itself, and the users. - -## Transport Layer Security - -It is strongly recommended to run `radius-proxy` behind a reverse proxy (like Nginx or Caddy) that provides TLS (HTTPS) termination. This ensures that all communication between the user's browser, Grafana, and the proxy is encrypted. - -## OAuth 2.0 and OIDC Security - -### PKCE (Proof Key for Code Exchange) - -- **What it is**: PKCE (RFC 7636) is an extension to the Authorization Code flow that mitigates the threat of authorization code interception. -- **Implementation**: `radius-proxy` fully supports PKCE. When Grafana (or another client) initiates a login with a `code_challenge` and `code_challenge_method`, the proxy stores these values along with the authorization code. During the token exchange, the client must provide a `code_verifier`. The proxy then validates the verifier against the stored challenge. -- **Benefit**: This ensures that even if an authorization code is stolen, it is useless without the corresponding `code_verifier`, preventing attackers from exchanging it for a token. -- **Code Reference**: `app/api/oauth/authorize/route.ts` (stores the challenge) and `app/api/oauth/token/route.ts` (verifies the code). - -### Strict Redirect URI Validation - -- **What it is**: Open redirect is a vulnerability where an attacker can use a legitimate application to redirect users to a malicious site. In OAuth 2.0, this can be used in phishing attacks. -- **Implementation**: The proxy enforces a strict allowlist for redirect URIs. The `redirect_uri` provided by the client during the authorization request **must exactly match** one of the URIs in the `REDIRECT_URIS` configuration array. -- **Benefit**: This prevents attackers from tricking the proxy into redirecting users with a valid authorization code to a server they control. -- **Code Reference**: `app/api/oauth/authorize/route.ts` (`POST` handler). - -### State Parameter - -- **What it is**: The `state` parameter is used to protect against Cross-Site Request Forgery (CSRF) attacks. -- **Implementation**: The proxy correctly echoes back the `state` parameter provided by the client in the authorization request to the final redirect. It is the client's (Grafana's) responsibility to generate a unique `state` value and validate that it matches upon receiving the callback. - -## JWT (JSON Web Token) Security - -### Strong Signing Algorithms - -- **Default**: The proxy defaults to using **RS256** (RSA with SHA-256) for signing JWTs. This is an asymmetric algorithm, meaning it uses a private key to sign tokens and a public key to verify them. -- **Benefit**: This is highly secure, as the private key never leaves the server. The public key can be safely distributed via the `/jwks.json` endpoint for clients like Grafana to use for verification. -- **Key Management**: The proxy automatically generates a 2048-bit RSA key pair on first startup and stores it in the `.keys/` directory. This directory should be protected and persisted across deployments. -- **Alternative**: For simpler setups, `HS256` (HMAC with SHA-256) is supported via the `JWT_HS256_SECRET` environment variable. This is a symmetric algorithm and is less secure if the secret is compromised. -- **Code Reference**: `lib/jwt.ts`. - -### Token Expiration - -- **Access Tokens**: Are short-lived (default: 1 hour) to limit the window of opportunity for an attacker if a token is compromised. -- **ID Tokens**: Also short-lived (default: 1 hour). -- **Refresh Tokens**: Are long-lived (default: 90 days) but can only be used to obtain new access tokens and are stored securely by the proxy. -- **Authorization Codes**: Are extremely short-lived (default: 10 minutes) and are single-use. - -## Application Security - -### HTTP Security Headers - -To protect against common web vulnerabilities, the proxy adds several security headers to its HTTP responses, particularly for user-facing pages. - -- `Content-Security-Policy`: Set to `default-src 'self'` to prevent cross-site scripting (XSS) by restricting where content can be loaded from. -- `X-Content-Type-Options`: Set to `nosniff` to prevent the browser from MIME-sniffing a response away from the declared content-type. -- `X-Frame-Options`: Set to `DENY` to prevent the login page from being embedded in an `