[优化] 更新文档

This commit is contained in:
ryan
2026-06-01 23:19:32 +08:00
parent a850b0a188
commit 2525664013
76 changed files with 6493 additions and 2497 deletions
+181
View File
@@ -0,0 +1,181 @@
# Agent Design Document
You will learn: Agent design principles, core functional modules, interaction links with the Server, and how configuration applications are secured and made reliable through immutable version models and the three-stage disaster recovery rollback mechanism.
---
## Requirements Analysis
In distributed reverse proxy and edge security gateway scenarios, the Agent plays a central role in connecting the control plane (Server) and the data plane (OpenResty). Since the Agent runs on the user's actual node server, its design must adhere to the following core security and high-availability requirements:
1. **Active Pull (Pull Model) instead of Push**: The Server does not hold the SSH keys of the nodes, nor does it actively initiate inbound connections to the nodes. All control directives and configuration updates are actively pulled by the Agent via heartbeats or long-lived connections (WebSockets). This eliminates inbound firewall security risks on the node side and prevents control channels from being hijacked.
2. **Minimal Intrusiveness**: The Agent runs as an independent Go binary process. It only interacts with the local OpenResty process through file-based configuration rewriting and signal notifications, without interfering with other system services on the node.
3. **Robust Disaster Recovery & Self-Healing**: Since network jitter, disk exhaustion, or erroneous configurations can easily lead to configuration sync failures, the Agent must possess zero-dependency local rollback and self-healing capabilities, strictly preventing a single configuration error from causing a complete node outage.
4. **Pure Data and State Landing**: The Agent is only responsible for executing file generation and control intentions rendered by the Server. It does not carry complex control plane duties like business logic validation or multi-tenant authorization, ensuring the node side remains highly efficient and lightweight.
---
## Core Capabilities
The Agent is composed of the following core sub-modules, cooperating to manage its complete lifecycle:
| Module Name | Directory | Responsibilities |
| :--- | :--- | :--- |
| **Config Sync** | `sync/` | Pulls full configuration packages, writes files, triggers reloads, and records and reports sync statuses. |
| **Heartbeat** | `heartbeat/` | Periodically reports node health and resource metrics to the Server and retrieves the latest active version summary. |
| **WebSocket** | `wsclient/` | Maintains a persistent connection with the Server, providing sub-second real-time configuration pushes and commands. |
| **OpenResty Control** | `nginx/` | Executes Nginx config validation (`openresty -t`), rewrites, graceful reloads (`reload`), and process auto-start. |
| **Local State Store** | `state/` | Persistently records local applied versions, error logs, and buffers unsent observability metrics. |
| **Self-Updater** | `updater/` | Listens to Server self-update commands, securely pulls new binary versions, and completes in-place upgrades. |
| **Observability** | `observability/` | Collects host CPU/memory/disk and Nginx performance metrics, processes access logs, and uploads them. |
| **GeoIP Maintenance** | `geoipdata/` `geoipupdate/` | Maintains and updates the local GeoIP database periodically to support WAF country-level filtering. |
---
## Interaction Flows with Server
The Agent communicates with the control plane through **Token-based Auto-Registration** and a **Dual-channel Heartbeat/WebSocket** system during its lifecycle.
### 1. Auto-Registration Flow
If the Agent starts with an empty `access_token` in its local `agent.json`, but has a `discovery_token` configured, it triggers the auto-registration flow:
1. The Agent sends a registration request to `/api/agent/register`, carrying a local hardware fingerprint, IP, and hostname.
2. After validating the `discovery_token`, the Server generates a unique `NodeID` and a dedicated `AccessToken` (i.e., `agent_token`) in the database and returns them.
3. The Agent writes the dedicated Token to its local configuration file, clears the one-time `discovery_token`, and uses the `AccessToken` for all subsequent authenticated communications.
### 2. Dual-Channel Heartbeat & Sync Mechanism
* **HTTP Polling (Fallback and Detection)**: The Agent sends POST heartbeat packets at configured `heartbeat_interval` intervals by default. It reports health metrics while retrieving the currently active configuration version summary (Version & Checksum).
* **WebSocket Channel (Real-time Communication)**: Upon a successful HTTP heartbeat, the Agent automatically attempts to upgrade the connection to WebSocket (`/api/agent/ws`).
* Once the WS connection is established, heartbeats and metrics reporting shift entirely to the WS pipeline, reducing network overhead.
* When the Server publishes or activates a new version, it broadcasts a notification to the Agent via WS. The Agent triggers the synchronization flow **immediately** upon receiving the change event, achieving sub-second configuration deployment.
* If the WS connection drops due to network issues, the Agent automatically falls back to HTTP polling and uses an exponential backoff algorithm to attempt rebuilding the WS channel.
### 3. Interaction Sequence Diagram
```mermaid
sequenceDiagram
autonumber
participant Agent as OpenFlare Agent
participant OR as Local OpenResty
participant Server as OpenFlare Server
Note over Agent: First Startup (No AccessToken)
Agent->>Server: 1. Auto-registration request (carrying discovery_token)
Server-->>Agent: 2. Issue NodeID & dedicated AccessToken (agent_token)
Note over Agent: Store Token in local configuration file
rect rgb(240, 248, 255)
Note over Agent, Server: HTTP Fallback & WebSocket Upgrade
Agent->>Server: 3. Send HTTP Heartbeat (report system metrics & health)
Server-->>Agent: 4. Return ActiveConfig summary & AgentSettings
Agent->>Server: 5. Initiate WebSocket upgrade request (/api/agent/ws)
Server-->>Agent: 6. Upgrade successful (persistent bi-directional channel)
end
rect rgb(245, 245, 245)
Note over Agent, Server: Real-time Configuration Publication
Note over Server: Administrator clicks publish config in UI
Server->>Agent: 7. Broadcast active config summary via WS (WSMessageTypeActiveConfig)
Agent->>Server: 8. Request full configuration details (carrying target Version/Checksum)
Server-->>Agent: 9. Return complete configuration snapshot (Nginx configs, certs, WAF rules, etc.)
Note over Agent: Backup old files, write new config to local temp path
Agent->>OR: 10. Execute config syntax validation (openresty -t)
OR-->>Agent: 11. Return validation result (OK)
Agent->>OR: 12. Send graceful reload signal (openresty -s reload)
Agent->>Server: 13. Report application success status (Apply Log & ActiveVersion)
end
```
---
## Control of OpenResty
The Agent implements end-to-end closed-loop control of the data plane OpenResty, including configuration rendering, syntax validation, graceful reloading, and exception state capturing:
### 1. Configuration Layout on Disk
Upon successful sync, the Agent writes configuration files to `/etc/nginx/openflare-lua/` (or the configured `LuaDir`) according to a strict physical structure:
* `nginx.conf`: Main configuration file (replaces absolute path placeholders, configures performance parameters, shared dictionaries, and global server blocks).
* `routes.conf`: Route configuration file (generated by the Agent, containing all website server blocks, certificate paths, cache settings, and rate limit directives).
* `certs/`: Certificate storage directory (files named as `{cert_id}.crt` and `{cert_id}.key`).
* `waf/` and `pow/`: Dedicated Lua runtime scripts required for WAF and CC mitigation.
* `waf_config.json` and `waf_ip_groups.json`: Structured rules and IP databases required by the WAF filtering engine.
### 2. Refined Reload Operations
1. **Backup Current Config**: Before writing new files, the Agent copies the existing configuration files to a `.backup` directory, keeping a complete rollback snapshot.
2. **Write and Replace Placeholders**: Writes the pulled templates, automatically replacing absolute path placeholders (e.g., `__OPENFLARE_LUA_DIR__`) with actual local execution paths.
3. **Syntax Validation**: Calls `openresty -t -c <temp_nginx.conf>` to run a strict syntax test.
4. **Graceful Reload**: If validation passes, the Agent moves the files to the official paths and executes `openresty -s reload`. If OpenResty is not running, it launches the process.
5. **Exception Capture**: If validation or reload fails, the Agent intercepts the standard error output (stderr) and extracts the first 2000 characters of the detailed error log.
---
## Publishing & Config Application Model
OpenFlare discards the fragile mechanism of dynamically patching node configurations, instead using an **immutable configuration version publishing model**.
```text
Edit rules -> Preview / View diff -> Publish -> Generate full configuration version -> Activate version -> Agent pulls -> Local application -> Report result
```
### 1. Core Design Principles
* **Complete Publication**: Every publication compiles all enabled proxy routes, certificates, and global/custom WAF rules at once, generating a complete version package with a unique `checksum`.
* **Version Format**: Uses the `YYYYMMDD-NNN` incremental format, ensuring version histories are intuitive and strictly monotonic.
* **Global Single Active Version**: The system supports only one globally `active` configuration version at any given time. Rollbacks do not require reverse patching; they simply transition an older healthy version to the `active` state, and the Agent pulls and applies it.
### 2. Three-Stage Disaster Recovery & Rollback Mechanism
If the Agent fails to apply a configuration (or reload fails), it automatically triggers the following three-stage self-healing pipeline:
```mermaid
graph TD
A[Config Application Failed] --> B[Stage 1: Attempt Local Backup Recovery]
B -- Backup Exists --> C[Write Local Backup Files]
C --> D[Run openresty -t Validation]
D -- Validation OK --> E[Reload Old Configuration]
D -- Validation Failed --> F[Proceed to Stage 2]
B -- No Backup --> F[Stage 2: Write Built-in Safe Fallback Config]
F --> G[Write fallback nginx.conf: Listen on Port 80 Only]
G --> H[Enable stub_status health checks]
G --> I[Return 503 for all other routes & block errors]
G --> J[Attempt to launch OpenResty to maintain basic survival]
J --> K[Proceed to Stage 3]
E --> L[Report Apply Warning]
K --> M[Block Local Repeated Application of Failed Version]
M --> N[Report Apply Error with detailed logs]
```
1. **Stage 1: Local Backup Rollback**
* The Agent attempts to restore the main configuration, routes, and certificates from the `.backup` directory.
* It runs `openresty -t` validation on the restored backup. If successful, it reloads and reports a `Warning` to the Server (Warning: failed to apply new version, automatically rolled back to the previous healthy version).
2. **Stage 2: Built-in Safe Fallback Runtime**
* If no local backup exists (e.g., first deployment failed) or if the rollback validation fails, the Agent activates the ultimate self-healing mechanism: writing a **built-in safe fallback configuration**.
* **Fallback Configuration Specification**:
* Listens only on port `80`, containing no real user reverse proxy routes.
* The `/openflare/stub_status` endpoint returns a healthy response, while all other requests uniformly return a `503 Service Unavailable` status code with the fixed response body `OpenFlare: No Valid Configuration`.
* It attempts to launch OpenResty with this minimal configuration. This keeps the Nginx process alive, preserving underlying health probes and metric endpoints, preventing containers/pods from being repeatedly killed and restarted by orchestration systems, while keeping sensitive routes secure.
3. **Stage 3: Local Configuration Blocking**
* The Agent records the failing configuration's `version + checksum` in its local state store blacklist.
* Until the control plane activates a new configuration (resulting in a changed `checksum`), the Agent's heartbeat blocks repeated synchronization pulls of this erroneous version, preventing nodes from entering an infinite loop of "heartbeat -> pull failing config -> crash rollback".
### 3. WAF IP Group Asynchronous Runtime Synchronization
To prevent highly volatile IP blacklists from triggering frequent full config publications and Nginx reloads (which still incur minor CPU and connection overhead), WAF IP groups are synchronized via an **asynchronous differential sync design**:
* **Static Publication Snapshot**: The `waf_config.json` generated upon publication only contains the group ID reference mapping (i.e., `ip_whitelist_group_ids` / `ip_blacklist_group_ids`) and does not contain the actual list of IP addresses.
* **Heartbeat Differential Check**: The Agent uploads its locally cached IP groups MD5 checksum map in its heartbeat.
* **Differential Delivery**: The Server compares checksums and only delivers missing or modified IP groups, which are written directly to `waf_ip_groups.json` on the node without reload.
* **WebSocket Real-time Push**: When an administrator updates an IP group, or a threat intelligence subscription successfully pulls, or a security rule triggers a temporary block, the Server immediately broadcasts the IP group update package via WebSocket. The Agent receives and applies it instantly **without Nginx reloads**.
---
## Design Constraints
To protect the security boundary of the data and control plane, Agent development must strictly comply with the following engineering constraints:
1. **Zero-Privilege Command Execution**: The Server is strictly prohibited from sending any arbitrary shell commands or scripts to the Agent (such as exec/eval). All system control operations (such as start, stop, reload, update) must be hardcoded inside the Agent binary.
2. **Strict Token Filtering and Prefix Validation**: Agent requests to the Server must be prefixed with `/api/agent/` and must carry the `X-Agent-Token` header for signature or token verification.
3. **Node Autonomy**: The Agent must support complete offline capabilities. During disconnected periods, the local OpenResty must rely on local configuration copies to keep reverse proxy services running normally.
+124 -47
View File
@@ -1,8 +1,10 @@
# System Architecture
You will learn: The overall architecture of OpenFlare, the responsibility boundaries of Server, Agent, OpenResty, and the management console frontend, and the request flow of a configuration release from the management console to take effect on a node.
You will learn: The overall architecture of OpenFlare, the boundaries of responsibilities for Server, Agent, OpenResty, and Admin Frontend, and the request flow of a configuration publication from the admin dashboard to activation on a node.
OpenFlare consists of the Server, the Agent, local OpenResty on each node, and the management console frontend. The Server is the control plane, the Agent is the only controlled landing entry point on the node side, and OpenResty is the actual data plane.
OpenFlare consists of the Server, the Agent, the node-local OpenResty, and the Admin Frontend. The Server is the control plane, the Agent is the only controlled entry point on the node side, and OpenResty serves as the actual data plane. In intranet penetration scenarios, the Relay (frps manager) and OpenFlared (frpc manager) extend the data plane traffic path.
### Standard Reverse Proxy Traffic Path
```text
Browser
@@ -24,75 +26,140 @@ OpenResty binary
Origin
```
### Intranet Penetration Traffic Path
```text
Browser
|
| HTTPS request
v
OpenResty (Agent, TLS/WAF) <-- TunnelRelay Node
|
| proxy_pass http://localhost:vhost_port (Host header preserved)
v
OpenFlareRelay (frps) <-- TunnelRelay Node, co-located with Agent
|
| frp tunnel protocol (HTTP Vhost routing by Host header)
v
OpenFlared (frpc) <-- Intranet Server
|
| HTTP/HTTPS forward
v
Internal Service (192.168.x.x)
```
## Component Responsibilities
| Component | Responsibility |
| --- | --- |
| Server | Management UI, admin APIs, Agent APIs, configuration rendering, version publishing, data storage, and aggregate queries |
| Agent | Registration, heartbeats, synchronization, writing files, configuration validation, reloads, fallback/rollbacks, self-updating, and lightweight data collection |
| OpenResty | Receives real traffic, executes WAF, PoW, authentication, and reverse proxying according to configurations rendered by OpenFlare |
| Frontend | Manages website configurations, WAF, origins, certificates, nodes, versions, users, settings, and observability pages |
| Server | Admin UI, Admin API, Agent/Relay/Client API, configuration rendering, version publishing, data storage, and aggregated queries. |
| Agent | Registration, heartbeats, synchronization, file writing, validation, reload, rollback on failure, self-updating, and light metrics collection. |
| OpenResty | Receives real traffic, executing WAF, PoW, authentication, and reverse proxying according to the configuration rendered by OpenFlare. |
| OpenFlareRelay | Manages the lifecycle of the frps process, providing tunnel relay services and receiving frps configurations via heartbeat. |
| OpenFlared | Manages frpc processes (can be multiple), connecting to the Relay and forwarding traffic to intranet services. |
| Frontend | Manages pages for website configs, WAF, origins, certificates, nodes, tunnels, versions, users, settings, and observability. |
## Server
`openflare_server` is a monolithic control plane:
`openflare_server` is the single-control-plane monolith:
* Gin provides HTTP services.
* Gin provides the HTTP services.
* GORM accesses SQLite or PostgreSQL.
* The existing login system provides management console Sessions.
* Authentication source and external account binding support GitHub OAuth and standard OIDC.
* The Go Server hosts the static build output of `openflare_server/web`.
* The existing login system provides Admin Session management.
* Authentication sources support GitHub OAuth and standard OIDC logins with external account binding.
* The Go Server hosts the `openflare_server/web` static build assets.
The Server does not directly SSH into nodes, nor does it modify node files online. It only saves the control plane state, generates complete configuration versions, and lets nodes actively pull them via the Agent API.
The Server does not directly SSH to nodes, nor does it modify node files online. It only stores control plane state, generates complete configuration versions, and lets nodes actively pull them via the Agent API.
## Agent
`openflare_agent` is a Go monolithic application:
* Runs on nodes as a single binary.
* Reads or generates local node information upon startup.
* Performs periodic heartbeats to report status and fetch the active version summary.
* Pulls configurations, backs up old files, writes new files, validates, and reloads upon discovering a new version.
* Attempts to restore execution and roll back when the application fails.
* Maintains the WAF GeoIP mmdb; writes the built-in initial database on startup and updates it regularly based on configuration.
* Runs as a single binary on the node side.
* Reads or generates local node information on startup.
* Performs periodic heartbeat check-ins to report status and retrieve active version summaries.
* Upon discovering a new version, it pulls the configuration, backs up old files, writes new files, validates them, and reloads.
* Automatically rolls back to restore operations if the application fails.
* Maintains the local WAF GeoIP mmdb, writing the built-in library on startup and updating it periodically based on configuration.
The Agent uniformly executes validations, reloads, starts, and restarts via the OpenResty binary pointed to by `openresty_path`; it falls back to calling `openresty` by default when not configured. In Docker deployments, the Agent image includes the OpenResty binary and follows the same binary control logic.
The Agent executes validation, reload, startup, and restart uniformly via the path specified in `openresty_path`; if unconfigured, it defaults to calling `openresty`. During Docker deployments, the Agent image packages OpenResty and follows the same execution control logic.
Node IPs are maintained by Agent registration and heartbeat reports by default; when the admin UI locks a node IP, the Server continues updating runtime fields such as status, versions, and observability, but no longer accepts Agent reports to overwrite that IP.
The node IP is maintained by default through Agent registration and heartbeat reporting; if the administrator locks the node IP, the Server only updates running status, versions, and observability fields, and no longer accepts reports from the Agent to override the locked IP.
## Frontend
`openflare_server/web` is the official management console frontend:
`openflare_server/web` is the official Next.js-based frontend:
* Next.js App Router.
* Next.js 15 App Router.
* React 19.
* TypeScript.
* Tailwind CSS.
* TanStack Query manages server state.
* TanStack Query for server-side state.
The frontend is hosted by the Go Server after static export. All API requests must go through `lib/api/` uniformly and handle the `success/message/data` response structure.
The frontend uses static export mode (`output: 'export'`), which is then hosted by the Go Server using `embed.FS`. All API requests must go through `lib/api/` and process the `success/message/data` response structure.
## Data and Request Flow
The Server integrates the following security features:
* CORS middleware: Cross-Origin Resource Sharing protection.
* Rate limiting: Global and key API endpoint throttling.
* Session management: Cookie/Redis-based session storage.
### Management Console Request Flow
## Data & Request Flow
### Management Request Flow
```text
Browser -> Frontend -> /api/* -> controller -> service -> model -> database
```
Mutation APIs on the management console use `POST`, while read-only APIs use `GET`. Both success and failure return a clear `message`.
Admin mutation APIs use `POST`, while read-only APIs use `GET`. Both success and failure responses return a clear `message`.
### Agent Sync Flow
```text
Agent heartbeat -> Server returns active version summary
Agent discovers new version -> Pulls configuration details
Agent writes main configuration / route configuration / certificates / Lua resources / WAF runtime configuration
Agent executes OpenResty validation and reload
Agent HTTP heartbeat -> Server returns active version summary
Agent detects new version -> Pulls complete configuration details
Agent writes main configuration / route configurations / certificates / Lua resources / WAF runtimes
Agent runs OpenResty validation (openresty -t) and reload
Agent reports application result
```
When WebSocket (WS) connection upgrade is enabled by default, the Agent first obtains settings through the HTTP heartbeat, and then attempts to connect to the Agent WebSocket. Once the WS connection is successful, periodic status reporting is carried by WS; when the Server publishes or activates a version, it broadcasts the active version summary to connected Agents, allowing them to enter the synchronization flow immediately. When the WS connection is disconnected or fails to establish, the Agent automatically falls back to the HTTP heartbeat.
### Relay Sync Flow
The Relay (OpenFlareRelay process) runs on the TunnelRelay node and shares the same `agent_token` with the Agent:
```text
Relay HTTP heartbeat -> Server returns frps base configuration (bindPort, vhostHTTPPort, auth_token)
Relay generates frps.toml and starts or updates the frps process
Relay periodically reports frps health status and connection statistics
Relay attempts WebSocket upgrade for real-time configuration pushes
```
frps configurations are relatively static (ports, auth token), dispatched via heartbeats, and **not included in the versioned publishing flow**. The Relay must monitor the frps process and auto-recover it on failures. Authentication: `X-Agent-Token` + API path prefix `/api/relay/*`, distinguished by Server via `node_type = tunnel_relay`.
### OpenFlared Sync Flow
OpenFlared (client) runs inside the intranet server, using independent `tunnel_token` authentication:
```text
Client HTTP heartbeat -> Server returns tunnel configuration version summary (version, checksum)
Client detects new version -> Pulls complete tunnel route configuration (relay list + frpc proxy definitions)
Client generates independent frpc.toml configuration files for each Relay
Client starts a new frpc process for new Relays, or hot-reloads (frpc reload) existing ones
Client reports application results (success/failure details)
```
OpenFlared communicates with the Server via `/api/flared/*` using the `X-Tunnel-Token` header. Tunnel route configurations are versioned along with the publishing flow, ensuring all configuration changes are consistently published to both Agents and Clients via a single version number.
**WebSocket Upgrade Flow** (Optional, controlled via `AgentWebsocketUpgradeEnabled`):
When WebSocket upgrade is enabled:
1. The Agent retrieves run configurations and settings via HTTP heartbeat.
2. The Agent attempts to upgrade the connection to `GET /api/agent/ws` (WebSocket).
3. Once the WS connection is established, periodic state reporting and real-time commands are carried over the WebSocket pipeline, minimizing latency.
4. When the Server publishes or activates a version, it immediately broadcasts the active version summary to connected Agents, triggering the sync flow instantly.
5. If the WebSocket disconnects or fails to establish, the Agent automatically falls back to HTTP heartbeats, ensuring high availability.
Through the `OpenRestyWebsocketEnabled` option, WebSocket reverse proxy support can be enabled or disabled at the OpenResty layer.
### Reverse Proxy Flow
@@ -100,18 +167,21 @@ When WebSocket (WS) connection upgrade is enabled by default, the Agent first ob
Client -> OpenResty server block -> WAF Lua -> named upstream -> Origin
```
Website configuration is the aggregation boundary of reverse proxies. A website configuration can bind multiple domains and share site-level traffic limits, reverse proxies, and caching configurations.
Website configurations are the boundaries of reverse proxy aggregation. A single website configuration can bind multiple domains, sharing site-level rate limiting, reverse proxy, and cache settings.
WAF is executed in the OpenResty `access_by_lua_file` phase. Rules come from `waf_config.json` carried in the current active version; the global rule group takes effect by default, and websites can overlay custom rule groups.
WAF executes in the OpenResty `access_by_lua_file` phase. Rules originate from the `waf_config.json` carried in the currently active version; global rule groups take effect by default, and websites can overlay custom rule groups. `waf_config.json` only stores rule group references and IP group IDs; IP group members are synchronized independently by the Agent into `waf_ip_groups.json`, and the OpenResty Lua engine merges and evaluates them by reference ID.
WAF IP groups are managed by the Server. Manual IP groups store IP/CIDR lists directly; auto IP groups are evaluated by Server cron jobs reading request logs and applying Expr boolean rules; subscription IP groups are fetched by Server cron jobs from remote text or JSON sources. The Agent reports local IP group checksums in heartbeats, and the Server only returns mismatched IP groups. When an IP group is updated on the Server, a broadcast is sent via WebSocket to push changes, and the OpenResty Lua reads the local JSON file directly without querying the DB, request logs, or remote subscription sources.
## Core Objects
Currently active entities include:
Current valid entities include:
* `proxy_routes`
* `origins`
* `config_versions`
* `nodes`
* `tunnels`
* `auth_sources`
* `external_accounts`
* `node_system_profiles`
@@ -124,23 +194,30 @@ Currently active entities include:
* `traffic_analytics_rollups`
* `node_health_events`
* `waf_rule_groups`
* `waf_ip_groups`
* `waf_rule_group_bindings`
* `acme_accounts`
* `dns_accounts`
* `geoip_update_configs`
## Key Design Decisions
| Decision | Reason |
| Decision | Rationale |
| --- | --- |
| Complete configuration versions, instead of online patches | Gives previews, activations, history, and rollbacks stable boundaries |
| Active pull by Agents | Server does not need SSH permissions, nor does it expose remote command execution entry points |
| Global single active version | Reduces MVP complexity and ensures all nodes are consistent by default |
| Website configurations aggregate multiple domains | Supports sharing site-level policies for a business site while allowing certificate binding per domain |
| Server-side aggregation of observability data | Avoids inconsistent results caused by temporary frontend calculations |
| Full Config Versioning instead of Patches | Provides stable, verifiable boundaries for previewing, activating, history, and rollbacks. |
| Pull Model (Agent-driven) | Server does not need SSH keys or inbound command ports, preventing control channel hijacking. Supports HTTP and WebSocket. |
| Global Single Active Version | Reduces MVP complexity, ensuring all nodes are uniform by default. Supports previews, version history, and one-click rollback. |
| Website Multi-Domain Aggregation | Enables sharing site-level policies across domains while supporting per-domain certificate binding. |
| Server-side Observability Aggregation | Prevents UI-side temporary statistical calculations from producing inconsistent data metrics. |
| Intranet Penetration based on frp | Reuses a mature tunnel protocol rather than custom implementations to minimize stability risks. frps Vhost routing aligns naturally with HTTP. |
| Independent Binary for Relay/Client | Separation of concerns: Relay manages frps, Client manages frpc, allowing independent updates and deployments. |
| Tunnel decoupled from Node system | Tunnel clients run internally, using completely different registration and authentication flows compared to edge nodes. |
## Contributor Reading Suggestions
## Recommended Reading for Contributors
If you want to modify architecture-related code, read these first:
Before modifying architectural code, please read:
1. [Product Boundary](./index.md)
2. [Release Model](./release-model.md)
3. [Development Constraints](./development.md)
4. [Repository Structure](../reference/repository.md)
1. [Product Boundaries](./index.md)
2. [Agent & Publish Model](./agent-design.md)
3. [Development Constraints](../../guildline/development-constraints.md)
4. [Repository Structure](./repository.md)
+125 -258
View File
@@ -1,315 +1,182 @@
# Development Constraints
# Local Development
You will learn: The admission criteria for OpenFlare code modifications, backend/Agent/frontend tiered constraints, data model boundaries, API conventions, database migration requirements, and test delivery baselines.
You will learn: How to build OpenFlare's local development environment, start the Server, the Agent, and the Admin Frontend, run test and build commands, and understand the boundaries to respect before contributing code.
This document integrates the original development specifications, frontend specifications, and development plans, and serves as the engineering constraints entry point for OpenFlare after `1.0.0`.
This page is aimed at contributors. Product boundaries, data model constraints, API conventions, and frontend layering specifications are governed by [Development Constraints](../../guildline/development-constraints.md); this page only provides actionable workflows for local development.
## Current Conclusions
## Repository Structure
* The mainline capabilities of the first to sixth versions have all been completed.
* `1.0.0` is the current official baseline.
* Procedural tasks of completed stages are subject to code, tests, and Git history.
* Priority for new work is given to bug fixes, maintainability improvements, and documentation and test reinforcement.
For details on the physical directory structure and responsibilities of each module (Server, Agent, Frontend, etc.), see [Repository Structure](./repository.md).
Current Development Priorities:
## Environment Requirements
1. Stability.
2. Upgrade and rollback link reliability.
3. Document accuracy.
4. Test coverage reinforcement.
5. Small iterations within existing boundaries.
## Change Admission
Before new requirements enter implementation, judge them in the following order:
1. Whether it fits the [Product Boundary](./index.md).
2. Whether it follows the backend, Agent, and frontend constraints in this document.
3. Whether it risks breaking the existing publish, sync, rollback, or upgrade main links.
4. Whether it requires synchronized updates to deployment, configuration, README, or documentation site pages.
If a requirement expands the boundary or introduces new infrastructure, the design documentation must be updated first before starting implementation.
Any changes merged into the official baseline must at least meet:
* Does not break the Agent heartbeat, synchronization, publishing, and rollback main links.
* Does not break the existing OpenResty main configuration hosting model.
* Does not degrade the existing availability of the overview, node details, and access analysis.
* Has tests or joint debugging verification commensurate with the risks.
* Documentation remains consistent with the code.
## Technical Baseline
Server:
* Go 1.25+
* Gin
* GORM
* SQLite / PostgreSQL
* Existing login system
Agent:
* Single binary
* Node-local execution
* Control OpenResty binary via `openresty_path` or default `openresty`
* Docker deployment uses the Agent image with built-in OpenResty, and does not have the Agent control a separate OpenResty container
Frontend:
* Next.js 15 App Router
* React 19
* TypeScript 5
* Tailwind CSS 4
* TanStack Query
* React Hook Form + Zod
* Zustand only used for lightweight client status
* ESLint + Prettier
* Vitest + Testing Library + Playwright
* pnpm
## Server Layering
| Directory | Responsibility |
| Item | Requirement |
| --- | --- |
| `controller/` | Parameter parsing, calling services, returning responses |
| `service/` | Business logic, verification, transaction orchestration, rendering |
| `model/` | Model definition and persistence |
| `router/` | Route registration |
| `middleware/` | Auth, authorization, rate limiting, and other cross-cutting logic |
| `common/` | Configuration, global state, and initialization entry points |
| `utils/` | Pure utility functions and general helpers |
| Go | `1.25+` |
| Node.js | `18+` |
| pnpm | Recommended enabling via `corepack enable` |
| Docker | Required for Server containers, local integration testing, and Agent Docker images |
| OpenResty | Required to execute `openresty` locally when running the Agent |
| PostgreSQL | Optional; if not configured, the Server defaults to SQLite |
It is forbidden to accumulate business logic in `controller/`, forbidden to implement business flows in `middleware/`, and forbidden to add platform-level abstractions for simple requirements.
## Initializing Frontend Dependencies
## Agent Layering
The Agent maintains its existing module boundaries:
* `config`
* `heartbeat`
* `sync`
* `openresty` / `nginx`
* `state`
* `httpclient`
* `protocol`
* `internal/updater`
Requirements:
* Each module has a single responsibility.
* External command calls are centrally encapsulated.
* State persistence and configuration persistence are separated.
## Frontend Layering
Recommended directories:
```text
app/
components/
features/
lib/
hooks/
store/
types/
styles/
tests/
```bash
cd openflare_server/web
corepack enable
pnpm install
```
Responsibility constraints:
Build the static assets hosted by the Go Server:
* `app/`: Routes, layouts, page assembly.
* `features/`: Organize modules by business domains.
* `components/`: Reuse components across features.
* `lib/`: Request client, environment variables, utility functions, constants.
* `store/`: A small amount of cross-page UI state.
* `types/`: Shared type definitions.
```bash
pnpm build
```
Page files are only responsible for obtaining routing parameters, organizing page structures, and calling feature components; they should not handwrite complex API details, complex form verification logic, or maintain a large amount of mutually coupled local states.
## Starting the Server
## Data Model Specifications
SQLite Mode:
Currently active entities:
```bash
cd openflare_server
export SESSION_SECRET='dev-session-secret'
export SQLITE_PATH='./openflare-dev.db'
export LOG_LEVEL='debug'
go run .
```
* `proxy_routes`
* `origins`
* `config_versions`
* `nodes`
* `auth_sources`
* `external_accounts`
* `node_system_profiles`
* `apply_logs`
* `tls_certificates`
* `managed_domains`
* `node_request_reports`
* `node_access_logs`
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
* `options`
* `waf_rule_groups`
* `waf_rule_group_bindings`
PostgreSQL Mode:
General constraints:
```bash
cd openflare_server
export SESSION_SECRET='dev-session-secret'
export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
export LOG_LEVEL='debug'
go run .
```
* No new platform-oriented objects are added unless explicitly required by the design document.
* `origins` only serves as a reusable origin address directory, and the fields are kept lightweight.
* `proxy_routes` uses "site configuration" as the aggregation boundary and must contain a unique `site_name` and a non-empty `domains` list.
* Each domain in `proxy_routes.domains` must be globally unique, and the first item in the list is treated as the primary domain.
* `proxy_routes` continues to allow saving one or more upstream addresses for load balancing, but does not introduce an independent `origin_pool`.
* The legacy `domain` field can only be used as a compatible mirror of `domains[0]`; new code must not continue to use this field as the unique business input.
* If `proxy_routes` is associated with `origins`, it must also save the `origin_url` that can be directly rendered.
* Upstreams uniformly use named `upstream` + keepalive; for a single upstream carrying a base path or query, the original URI should be added back to `proxy_pass`. For multiple upstreams, only pure `scheme://host[:port]` is allowed.
* Rate limits, reverse proxy, and cache configurations currently belong to the site-level `proxy_routes`.
* HTTPS certificate binding must be saved on a per-domain basis through `domain_cert_ids` parallel to `domains`; domains not bound to a certificate must not participate in HTTPS rendering.
* WAF global rule groups are applied to all websites by default, while custom rule groups are bound to site configurations via `waf_rule_group_bindings`; they must be included in the complete configuration version snapshot during publishing.
* `config_versions` must save complete snapshots and rendering results.
* There can only be one activated version globally at a time.
* Rollback is achieved by reactivating older versions.
* `nodes` only retains control plane status and low-frequency summaries.
* Observability data must be associated with nodes and time windows, and snapshots and aggregation results use an append-only model.
* Original access details must have a controlled retention policy.
* `auth_sources` only saves management console third-party login source configurations, currently supporting `github` and `oidc`.
* `external_accounts` is the unique source of binding between third-party accounts and local users; the old `users.github_id` is only used for compatible migration and must not be used as the business input for the new login flow.
Default access URL:
## Database Migration
```text
http://localhost:3000
```
Any modification involving table structures, indexes, column types, sharding rules, or internal persistence metadata must upgrade the database version number in sync.
The default credentials are `root` / `123456`.
The database version number is defined in `openflare_server/model`, and it must not rely solely on `AutoMigrate` for implicit upgrades of existing databases.
## Starting the Frontend Dev Server
Every time the database version number is upgraded, an explicit migration method from the previous version to the new version must be added. The migration method must contain validation logic after the upgrade; only when the validation passes can the new database version record be written.
The frontend dev server listens to port `3001` by default and proxies requests to the backend via `NEXT_DEV_BACKEND_URL`:
Versions 1 through 7 are treated as the historical initial baseline and no longer keep per-version upgrade files. Starting from v8, database migrations must be placed under `openflare_server/model/migrate` and named after the target version, such as `v16.go`. Each version file registers its migration through `init()`, and the current database version is derived from the highest registered target version. Do not change the semantics of released v8+ migrations merely to reorganize files.
```bash
cd openflare_server/web
export NEXT_DEV_BACKEND_URL='http://127.0.0.1:3000'
pnpm dev
```
When performing a database upgrade, complete the following steps:
Access:
1. Decide whether a schema version bump is required: any addition, removal, or rename of tables, columns, indexes, constraints, column types, sharding rules, or persisted-data semantics must upgrade the version.
2. Add `openflare_server/model/migrate/vN.go`, where `N` is the target version. The file header must include a comment explaining what this upgrade changes and why it is needed.
3. Implement `VN()` in `vN.go`, and call `Register(VN())` from `init()`. `FromVersion` must be `N-1`, and `ToVersion` must be `N`.
4. Implement the upgrade logic in `migrateVN`. Use `Context` to call shared capabilities such as `ApplyCurrentSchema`, historical backfills, and default-data initialization; complex data repairs must be explicit and must not rely on `AutoMigrate` alone.
5. Implement post-upgrade validation in `validateVN`. Validation must cover at least the existence of new tables/columns/indexes, required default data, and required data backfills.
6. If the migration needs new shared backfill or validation helpers, place them in `openflare_server/model/migrations.go` or another suitable model file, and expose them through `Context` to `model/migrate`; avoid reverse-importing `model` from the subpackage and creating an import cycle.
7. Add migration tests covering at least upgrade from the `N-1` old database to `N`, including schema version, table/column structure, key data backfills, and validation results. The `model/migrate` registry test checks version continuity, but business-specific migrations still require tests.
8. Update design/development docs; if management APIs, configuration fields, or user-visible behavior change, also update the relevant guides, configuration reference, and Swagger documents.
```text
http://localhost:3001
```
After starting the new package, the database's current version must be checked first, and then upgraded step by step in order to the target version; skipping intermediate upgrade steps to directly write the target version is prohibited.
## Starting the Agent
An empty database initialization can directly establish the current version structure, but the same-version validation must still be executed after the initialization is completed, and the current database version must be persisted.
If the migration or validation fails, the startup process must abort, and the database version record must not be upgraded. Submissions involving database version changes must add corresponding migration tests or equivalent regression tests.
## API and Authentication
The management console and Agent APIs uniformly use JSON. Both success and failure must return a clear `message`:
Create a local `agent.json`:
```json
{
"success": true,
"message": "",
"data": {}
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
Conventions:
Run:
* Agent APIs are uniformly placed under `/api/agent/*`.
* The overview and node details prioritize using dedicated aggregation interfaces.
* Management console mutation APIs uniformly use `POST`; read-only APIs use `GET`.
* The management console continues to reuse existing logins, roles, and Sessions.
* Third-party login uniformly enters through authentication source APIs; authentication source management interfaces must require Root Session.
* `/api/status` can only return the public fields of enabled authentication sources, and must not return the Client Secret.
* When a third-party account is not bound and registration is closed, a process to bind to an existing account should be provided, and users must not be automatically created.
* Official Agent requests uniformly use the node-exclusive `agent_token`.
* The first access can use the global `discovery_token`.
* Agent request headers uniformly use `X-Agent-Token`.
```bash
cd openflare_agent
export LOG_LEVEL='debug'
go run ./cmd/agent -config ./agent.json
```
It is forbidden to expose remote shell or arbitrary command execution entries, forbidden to print full Tokens in logs, and forbidden to save main configuration templates that bypass placeholder constraints.
If `openresty_path` is not configured, the Agent calls `openresty` by default. For debugging, you can explicitly configure `openresty_path`, `main_config_path`, `route_config_path`, `access_log_path`, `cert_dir`, `lua_dir`, and `runtime_config_dir`.
## Publishing and Runtime
## Running Tests
The publishing logic must maintain:
Server:
* Read all enabled `proxy_routes` during publishing.
* Read OpenResty main configuration parameters, reverse proxy performance parameters, and cache parameters at the same time.
* Generate complete OpenResty configuration.
* Calculate `checksum`.
* Write to `config_versions`.
* Activate the version by switching `is_active`.
```bash
cd openflare_server
GOCACHE=/tmp/openflare-go-cache go test ./...
```
Version constraints:
Agent:
* The version number format is fixed as `YYYYMMDD-NNN`.
* Do not modify historical versions online.
* Do not make differentiated versions grouped by nodes.
* Preview and diff are read-only capabilities and do not generate release records.
```bash
cd openflare_agent
GOCACHE=/tmp/openflare-go-cache go test ./...
```
The Agent must satisfy:
Frontend:
* Read or generate local `node_id` after startup.
* Periodic heartbeats and synchronization.
* Conventional synchronization prioritizes judging based on the version summary returned by the heartbeat.
* When WS connection upgrade is enabled and the connection is successful, the Agent can receive active version summaries via WS and immediately synchronize; WS failure or disconnection must fall back to HTTP heartbeats.
* Back up old files first when discovering a new version.
* Write main configurations, route configurations, and necessary certificate files.
* Write WAF/PoW runtime configurations, and ensure WAF Lua resources are managed uniformly by the Agent.
* Execute `openresty -t -c <main_config_path>` after writing the new configuration, and then reload; direct startup of OpenResty is allowed when reload finds that it is not running.
* Periodic runtime health checks must not call `openresty -t`, preventing health probes from triggering synchronous upstream domain name resolutions; they should prioritize requesting `/openflare/stub_status` on the local `openresty_observability_port`, using HTTP `200 OK` as the basis for judging that the OpenResty main process and workers are serving.
* If the activation of the new configuration fails, the Agent must first try to restore execution with the target configuration, then roll back to the old configuration and pull up OpenResty again.
* Report warning when OpenResty recovers normally after rollback; if there is no historical main configuration to restore locally, it must be allowed to write the built-in safe fallback configuration and pull up an OpenResty runtime state that only listens to port `80` externally and uniformly returns `503 Service Unavailable` and `OpenFlare: No Valid Configuration`, while retaining the local `stub_status` health check entry. The fallback runtime state must not clear the blocked status of the failed target; the application logs must reflect that the target version failed but the fallback runtime has started. Report failure when there is a historical main configuration but it still cannot recover after rollback.
* Once a target `version + checksum` application fails and rolls back, the Agent must block repeated applications of this target in its local state.
* When the Agent maintains the local MaxMind mmdb, download or refresh failures can only record warnings, and must not block heartbeats, synchronization, configuration application, or OpenResty health checks.
```bash
cd openflare_server/web
pnpm lint
pnpm typecheck
pnpm test
pnpm test:e2e
```
## Frontend Requests, State, and Types
Docs:
All API requests must be uniformly routed through `lib/api/`:
```bash
cd docs
pnpm build
```
* Uniformly handle the `success/message/data` response structure.
* Uniformly handle authentication failure, network exceptions, and general error messages.
* Centralize maintenance of resource interfaces and request paths.
## Building
State Layering:
Admin static assets:
* Server state: TanStack Query.
* Page temporary state: Component-internal `useState`.
* Cross-page UI state: Zustand.
```bash
cd openflare_server/web
pnpm build
```
Strict TypeScript mode is required; abuse of `any` is prohibited. API responses, form inputs, and business entities must have explicit types.
Server binary:
## Forms, Interaction, Style, and Themes
```bash
cd openflare_server
go build -o openflare-server .
```
Forms uniformly use React Hook Form and Zod.
Agent binary:
High-risk operations must have double confirmation, show the name of the operation object, and clearly provide success and failure feedback.
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
```
Style principles:
## Debugging Entrypoints
* Uniformly use Tailwind CSS and the existing token system.
* Prioritize reusing existing basic components and layout components.
* Maintain consistent visual hierarchy, padding, and semantic colors.
| Context | Command or Path |
| --- | --- |
| Server Logs | `LOG_LEVEL=debug go run .` |
| Agent Logs | `LOG_LEVEL=debug go run ./cmd/agent -config ./agent.json` |
| Swagger Docs | `http://localhost:3000/swagger/index.html` |
| Frontend API Proxy | `NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev` |
| OpenResty Validation | `openresty -t -c ./data/etc/nginx/nginx.conf` |
Theme requirements:
## Code Style & Change Admission
* Support `light`, `dark`, and `system` simultaneously.
* User choices must be persisted.
* Try to avoid theme flickering on the first screen.
Before contributing, verify:
## Test and Delivery
1. The requirement matches [Product Boundaries](./index.md).
2. The implementation conforms to [Development Constraints](../guildline/development-constraints.md).
3. The change does not disrupt publishing, sync, rollback, or upgrading lifecycles.
4. Update corresponding documentation if configurations, deployments, APIs, or boundaries change.
5. High-risk edits must be accompanied by unit tests or equivalent integration testing.
* Key business logic must have unit tests or equivalent regression tests.
* Agent main link modifications must verify synchronization, application, and rollback.
* Frontend pages must cover at least loading states, empty states, error states, and success feedback.
* When the Go version is adjusted, check `go.mod`, Dockerfile, and CI workflows in sync.
## Subsequent Maintenance
Subsequent planning is no longer maintained in the form of "major version phase documents", but adopts the following methods:
* Product boundary changes: Update [Product Boundary](./index.md).
* Engineering constraint changes: Update this document.
* Deployment and configuration changes: Update [Deployment Guide](../guide/deployment.md), [Configuration Items](../reference/configuration.md), and README.
If explicit new stage goals appear in the future, add dedicated planning documents separately; do not pile completed historical plans back into this document.
The model boundary of the current special topic "Site-level Rules and Configuration Interface Reconstruction" has been integrated into the [Product Boundary](./index.md). When executing, still advance in the order of data models, interfaces, frontend pages, migration tests, and document linkage.
Database schema alterations must elevate the database version number and supply explicit migration and validation methods from the previous version.
+151 -118
View File
@@ -1,171 +1,204 @@
# Product Boundary
# Product Boundaries
You will learn: What OpenFlare is, what problems it solves, who the target users are, what the current stable capabilities are, and which design boundaries cannot be bypassed during implementation.
You will learn: What OpenFlare is, what problems it solves, who the target audience is, what current stable features are available, and which design boundaries cannot be bypassed during implementation.
OpenFlare is a self-hosted OpenResty control plane oriented toward single-team or single-organization internal operation and maintenance (O&M) scenarios. It resolves the issues of scattered management in reverse proxy configuration, node synchronization, certificate hosting, configuration release/rollback, and basic observability.
OpenFlare is a self-hosted OpenResty control plane designed for single-team or single-organization internal operations. It solves the problems of decentralized management of reverse proxy configurations, node synchronization, certificate hosting, configuration publication and rollback, and basic observability.
## Project Positioning
OpenFlare is suitable for teams that need to centrally manage multiple OpenResty proxy nodes:
* Want to maintain reverse proxy site configurations using a management console.
* Want every configuration change to have a complete version, preview, activation, and rollback.
* Want nodes to actively sync configuration, rather than having the control plane SSH into nodes to execute commands.
* Want to manage TLS certificates, domain assets, node statuses, and basic access analytics within the same system.
* Wanting to maintain reverse proxy website configurations using a management dashboard.
* Wanting every configuration change to have a complete version history, preview, activation, and rollback support.
* Wanting nodes to actively synchronize configurations, rather than the control plane SSHing into nodes to execute commands.
* Wanting to manage TLS certificates, domain assets, node statuses, and basic access analytics in a single system.
OpenFlare is currently not positioned as a general-purpose log platform, service mesh, Kubernetes Ingress Controller, or multi-tenant cloud platform.
OpenFlare is currently not positioned as a general-purpose logging platform, service mesh, Kubernetes Ingress Controller, or multi-tenant cloud platform.
## Target Users
| User | Needs |
| --- | --- |
| Self-hosted users | Quickly deploy a visual OpenResty control plane |
| Internal O&M teams | Manage multiple reverse proxy nodes, certificates, and configuration versions |
| Development teams | Provide a unified entry point and basic access analytics for internal services |
| Contributors | Fix defects, strengthen tests, and improve documentation within clear boundaries |
## Current Stable Capabilities
## Current Capabilities
| Capability | Description |
| --- | --- |
| Reverse Proxy Rule Management | Uses site configuration as the aggregation boundary, supporting multi-domain and origin configuration |
| Site-level Configuration | One rule corresponds to one site, which can bind one or more domains and share site-level configuration |
| Origin Management | Maintains a lightweight origin directory and allows sites to save renderable origin snapshots |
| Configuration Versioning | Supports preview, publishing, activation, immutable history, and rollback |
| Agent Synchronization | Supports registration, heartbeat, synchronization, application result reporting, and self-updating |
| OpenResty Hosting | Manages main configuration templates, performance parameters, cache parameters, and Lua resources |
| HTTPS/TLS | Hosts certificates and domain assets, and binds certificates on a per-domain basis |
| WAF | Maintains IP/IP ranges black/whitelists and country-level geographical black/whitelists with global and website-customized rule groups |
| Basic Observability | Aggregates node requests, resource snapshots, health events, and access analytics |
| Node Management | Node status, token systems, deployment, and update links |
| Console Frontend | Next.js-based official management console |
| Auth Source Login | Supports configuring GitHub and standard OIDC login entries as authentication sources, allowing third-party accounts to bind to existing local users |
| Reverse Proxy Rules | Uses website configuration as the aggregation boundary, supporting multiple domains and origin settings. |
| Website-level Config | One rule corresponds to one website, which can bind one or more domains and share site-level configurations. |
| Origin Management | Maintains a lightweight origin directory and allows websites to save renderable origin snapshots. |
| Config Versioning | Supports previews, publishing, activation, immutable history, and rollbacks. |
| Agent Sync | Supports registration, heartbeats, synchronization, application result reporting, and self-updating. |
| OpenResty Hosting | Manages main config templates, performance parameters, cache parameters, and Lua resources. |
| HTTPS/TLS | Hosts certificate and domain assets, binding certificates on a per-domain basis. |
| WAF | Maintains IP/CIDR block blacklists/whitelists, IP groups, and country-level geographic access controls at both global and site-specific levels. |
| Basic Observability | Aggregates node requests, resource snapshots, health events, and access analytics. |
| Node Management | Manages node status, token systems, and deployment/update lifecycles. |
| Admin UI | Next.js-based official management dashboard. |
| Auth Source Login | Supports configuring GitHub OAuth and standard OIDC login portals, allowing third-party accounts to bind to existing local users. |
| Intranet Penetration | Securely exposes intranet HTTP services to the public internet using TunnelRelay nodes and the OpenFlared client, reusing the Agent's HTTPS/WAF capabilities. |
Default working method:
Default Working Model:
* All nodes consume the same globally activated version.
* The Server saves configuration and status, and does not directly manage nodes via SSH.
* The Agent is the only controlled landing entry point on the node side.
* All nodes consume the same globally activated configuration version.
* The Server stores configurations and state, and does not directly SSH to manage nodes.
* The Agent is the only controlled entry point on the node side.
* TunnelRelay nodes run both the Agent (OpenResty) and the Relay (frps manager) to provide intranet penetration relays.
* The OpenFlared client runs inside the intranet, managing the frpc process to connect to the Relay and forward traffic to intranet services.
## Typical Use Cases
| Scenario | Description |
| --- | --- |
| Unified Entry for Internal Services | Expose multiple internal HTTP services through a unified domain and certificate |
| Config Sync for Multi-node Reverse Proxy | Multiple OpenResty nodes consume the same activated configuration |
| Config Change Review | View preview or diff before publishing, and retain immutable history after publishing |
| Quick Rollback | Reactivate an older version, letting the Agent pull and apply it |
| Certificate Hosting | Bind TLS certificates for different domains |
| Basic Observability | View node status, request aggregation, access analytics, and health events |
| Unified Entrance | Exposes multiple internal HTTP services via a unified domain and TLS certificate. |
| Multi-Node Sync | Multiple OpenResty nodes consume the same active configuration version. |
| Change Review | View previews or diffs before publishing, keeping an immutable history post-publish. |
| Rapid Rollback | Re-activate an older version, letting the Agent pull and apply it. |
| Certificate Hosting | Bind TLS certificates to different domains under the same website. |
| Observability | Check node health status, aggregated requests, traffic analytics, and health events. |
| Intranet Penetration | Exposes intranet HTTP services that are not directly reachable from the public internet using Tunnels, benefiting from HTTPS, WAF, and all other protections. |
## Core Objects
## Website Configuration Constraints
Currently active entities:
* `proxy_routes`
* `origins`
* `config_versions`
* `nodes`
* `auth_sources`
* `external_accounts`
* `node_system_profiles`
* `apply_logs`
* `tls_certificates`
* `managed_domains`
* `node_request_reports`
* `node_access_logs`
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
* `waf_rule_groups`
* `waf_rule_group_bindings`
## Site Configuration Constraints
`proxy_routes` is upgraded from a "single-domain rule" to a "site configuration" aggregate object. One record corresponds to one website, which can bind one or more domains and share a set of site-level configurations.
`proxy_routes` is the aggregate object for "website configurations". One record corresponds to one website, which can bind one or more domains and share a set of site-level configurations.
Constraints:
* `proxy_routes.site_name` is the unique business identifier of the website.
* `proxy_routes.domains` contains at least one domain, and `domains[0]` is used as the primary domain.
* `proxy_routes.domains` must contain at least one domain, and `domains[0]` is treated as the primary domain.
* Any domain can globally belong to only one `proxy_routes`.
* During the migration period, `proxy_routes.domain` can be kept as a mirror field of `domains[0]`, but business read/write and subsequent extensions must be based on `site_name` + `domains`.
* Site-level rate limits, reverse proxies, and cache configurations are currently shared by site and are not configured differently on a per-domain basis within the same website.
* HTTPS allows binding certificates per domain within the same site.
* Site-level rate limits, reverse proxies, and caching configurations are shared by the site, with no per-domain differences allowed within the same website.
* HTTPS allows binding certificates on a per-domain basis within the same site.
## Origin Constraints
## Origin & Upstream Constraints
`origins` only saves the origin address, display name, and remarks, and does not carry protocols, ports, paths, weights, or health check policies.
`origins` serve the reuse of the origin directory, storing only the origin address, display name, and remarks, without carrying protocols, ports, paths, weights, or health check policies. `proxy_routes` can optionally associate with an `origins` record, but the rule internally still saves a complete upstream snapshot for rendering.
`proxy_routes` can optionally associate an `origins` record to reuse the origin address; the rule still saves a complete `origin_url` snapshot to participate in rendering and version snapshots.
Upstream Constraints:
Upstream constraints:
* `proxy_routes` must contain at least one upstream address (for direct type `direct`), or be associated with a Tunnel (for intranet penetration type `tunnel`).
* Multi-upstream load balancing is uniformly rendered into a named `upstream` with keepalive enabled.
* A single upstream is allowed to carry a base path or query, which is appended in `proxy_pass`. Multi-upstream is strictly limited to pure `scheme://host[:port]` structures, and all upstreams in the same rule must use the same protocol.
* `proxy_routes.origin_host` is an optional field used to override the `Host` header during back-to-source requests.
* All direct upstream addresses must be valid `http://` or `https://` URLs.
* Intranet penetration upstreams must associate with a valid `tunnel_id` and specify the intranet target address and protocol.
* `proxy_routes` must contain at least one upstream address.
* To maintain compatibility with historical data, the `origin_url` main upstream field is retained, and multiple upstreams are allowed to be added within the same rule for load balancing.
* Upstreams are rendered uniformly as a named `upstream` with keepalive.
* A single upstream can carry a base path or query and append it in `proxy_pass`.
* Multiple upstreams are restricted to pure `scheme://host[:port]`.
* `proxy_routes.origin_host` is an optional field, used to override the `Host` request header when back-origin.
* All upstream addresses must be legal `http://` or `https://`.
## Intranet Penetration Constraints
OpenFlare implements intranet penetration through TunnelRelay nodes and the OpenFlared client, built on top of frp (Fast Reverse Proxy).
### Node & Component Model
**Node Types**:
* `nodes.node_type` distinguishes the node type: `edge_node` (edge node, default) and `tunnel_relay` (tunnel relay).
* TunnelRelay nodes run both the Agent (OpenResty) and the Relay (frps manager) concurrently, sharing the same `agent_token`.
- The Agent is responsible for HTTPS termination, WAF protection, caching, and rate limiting.
- The Relay manages the frps process, providing tunnel relay services for intranet clients.
* TunnelRelay nodes introduce new fields: `node_type`, `relay_bind_port` (frpc connection port, default 7000), `relay_vhost_http_port` (HTTP Vhost port, default 8080), `relay_auth_token` (automatically generated), `relay_status`, etc.
**Tunnel Client**:
* The `tunnels` table independently stores intranet penetration client registration info and is decoupled from the `nodes` system.
* Each Tunnel has a unique `tunnel_id` (format `tun-<32hex>`) and `tunnel_token` (client authentication credential).
* The OpenFlared client runs inside the intranet, is not exposed to the public internet, uses `tunnel_token` for authentication, and communicates with the Server via `/api/flared/*` endpoints.
* An OpenFlared client can connect to multiple Relays simultaneously for high availability.
### Upstream Type Expansion
The upstream configuration of `proxy_routes` is divided into two types, distinguished by the `upstream_type` field:
* **Direct Upstream (`direct`, default)**: Forwards traffic directly to the origin address, behaving exactly like the existing mechanism.
* **Intranet Penetration Upstream (`tunnel`)**: Forwards traffic to the intranet service via a TunnelRelay node.
- Must specify `tunnel_id` (associated with the `tunnels` table).
- Must specify `tunnel_target_addr` (intranet target address, e.g., `192.168.1.100:8080`) and `tunnel_target_protocol` (`http` or `https`).
- During publication, the Server automatically replaces the upstream address with `http://127.0.0.1:{relay_vhost_http_port}`.
### Traffic Paths & Protocols
**Complete Data Plane Traffic Path**:
```
Browser → OpenResty (Agent, TLS/WAF) [TunnelRelay Node]
↓
frps (Relay, HTTP Vhost Routing) [TunnelRelay Node, 127.0.0.1:{vhost_port}]
↓
frp Tunnel Protocol (Host Header Routing)
↓
frpc (Client, Multi-process) [Intranet Server]
↓
Intranet Service (192.168.x.x:port)
```
**Key Features**:
* frps uses the HTTP Vhost single-port reuse mechanism; all HTTP tunnels share one `vhost_port`, automatically routed to the corresponding frpc based on the Host header.
* The Agent preserves the original `Host` header, which frps uses to match the virtual host.
* Each tunnel corresponds to a single `proxy_routes` and can bind multiple domains.
* The OpenFlared client manages an independent frpc process for each connected Relay, transmitting multiple HTTP proxy definitions via a single frp tunnel.
### Configuration Sync Model
The publication process generates two types of configuration version data simultaneously, linked by a single `config_version` version number:
* **Agent-side Config**: OpenResty main configuration + route configurations + WAF rules. If a tunnel upstream is included, it is automatically rendered as a `http://127.0.0.1:{vhost_port}` upstream.
* **Tunnel-side Config**: Relay list + frpc proxy definitions. Versioned alongside the publishing process; changes are hot-reloaded using `frpc reload` first.
* **Relay Config**: Dispatched via heartbeat responses, relatively static, and not included in the versioned publishing flow.
### Tunnel Design Constraints
* Only HTTP protocol tunnel traffic is supported (keeping TCP/UDP tunnels extensible); separate TCP/UDP port allocation is not supported for now.
* The DNS for domains using Tunnel upstreams should resolve to the designated TunnelRelay node.
* frp binaries (v0.61+) are packaged and provided by the system deployment script or container images.
## HTTPS Constraints
`proxy_routes.domain_cert_ids` is used to record domain-certificate bindings parallel to `domains`; a value of `0` indicates that HTTPS is not enabled for the domain, retaining only HTTP.
`proxy_routes.domain_cert_ids` is used to record the domain-certificate bindings parallel to `domains`; a value of `0` means the domain does not have HTTPS enabled and stays HTTP-only.
During publishing rendering:
During rendering:
* Domains with certificates are output as separate `443 ssl` `server` blocks grouped by certificate.
* Domains not bound to a certificate must not be automatically brought into HTTPS.
* All domains in `proxy_routes.domains` must be included in the same site configuration to avoid the same site being split in version snapshots.
* Domains with certificates are grouped by certificate and output as independent `443 ssl` `server` blocks.
* Domains without certificates bound must not be automatically routed to HTTPS.
* All domains in `proxy_routes.domains` must be kept in the same site configuration to avoid being split across version snapshots.
## WAF Constraints
WAF uses rule groups as configuration boundaries. The system fixes a global rule group, which is applied to all websites by default; websites can overlay multiple custom rule groups.
WAF centers around rule groups. The system provides a single global rule group (applied to all sites by default), on top of which websites can overlay multiple custom rule groups.
Phase 1 supports:
Core Capabilities:
* IP / IP range whitelists and blacklists.
* Country-level region whitelists and blacklists.
* Rule group-level blocking status codes and response pages, defaulting to `418` and an empty page.
* Supports individual IP / CIDR block whitelists and blacklists.
* Supports IP group references (including manual, automatic Expr calculated, and URL subscribed IP groups).
* Supports GeoIP-based country/region level admission filtering.
* Supports custom interception responses for rule groups (custom status codes and interception HTML pages, default is `418`).
Evaluation order:
IP Group & Judgment Constraints:
* Whitelists are bypass exceptions; if any enabled rule group matches a whitelist, the request is allowed.
* If no whitelist is matched, blacklists continue to be evaluated.
* When multiple blacklists match, the global rule group takes precedence, followed by custom rule groups in ascending order of their IDs.
Region recognition is based on the MaxMind mmdb maintained locally on the node by the Agent, and the OpenResty Lua reads the local database during the request path. When GeoIP dependencies are unavailable, region rules must be skipped, without affecting IP rules and the reverse proxy main link.
* **Runtime Decoupling**: The WAF runtime only reads local JSON files and does not access the Server database; configuration versions only store referenced IP group IDs. IP group members are synchronized via MD5 checksum differences and WebSocket push notifications, achieving hot activation without reloading Nginx.
* **Built-in Expr Rules**:
* High-frequency 404 scanning block: `request_count > 100 && status_404_ratio >= 0.8`
* Malicious IP direct probe: `ip_host_count > 50 && ip_host_ratio > 0.5`
* **Decision Priority**: The whitelist has absolute priority. If it does not match the whitelist, the blacklist funnel is triggered (global rule group first, custom groups matched in ascending ID order).
* GeoIP resolution depends on the local MaxMind database; if GeoIP is anomalous, region rules are automatically ignored and must not disrupt the availability of IP rules and the main reverse proxy chain.
## Authentication Source Constraints
`auth_sources` is the configuration object for third-party login entries on the management console, currently supporting only two types: `github` and `oidc`. Enabled authentication sources will be displayed on the login page.
`auth_sources` uniformly supports `github` and `oidc` login configurations. `external_accounts` stores bindings between third-party accounts and local users. Logic for first-time third-party login:
`external_accounts` saves the binding relationship between external accounts of authentication sources and local users. When a third-party account logs in for the first time:
* If already bound, directly authorize login; if there is an active local session, automatically bind.
* If unbound and registration is enabled, automatically create a local account; if registration is closed, require the user to provide an existing local username and password to establish the association.
* If it is bound to a local user, it logs in directly.
* If there is an existing local login session, it binds to the current user.
* If it is not bound and registration is allowed, a normal user is automatically created and bound.
* If it is not bound and registration is closed, the user is only allowed to enter an existing local account and password to complete the binding.
## Version & Observability Constraints
The old `users.github_id` only serves as a source for upgrade migration; new third-party account login and binding relationships must be based on `external_accounts`.
## Version and Observability Constraints
* `config_versions` must save complete snapshots, rendering results, and `checksum`.
* There can only be one activated version globally at a time.
* Rollback is achieved by reactivating older versions.
* `nodes` only carries control plane status and low-frequency summaries, not high-frequency observability facts.
* Metrics, trends, and access analytics prioritize server-side aggregation results, rather than temporary frontend statistics.
* Access details are only retained for controlled time windows, not evolving into a general-purpose log platform.
* `config_versions` must save the complete snapshot, rendering result, and `checksum`.
* Globally, only one version can be active at a time.
* Rollback is achieved by re-activating an older version.
* `nodes` only carry control plane state and low-frequency summaries; they do not carry high-frequency observability facts.
* Metrics, trends, and access analytics prioritize server-side aggregation rather than client-side temporary statistics.
* Access detail logs are only retained within a controlled time window, not evolving into a general logging platform.
## Documentation Maintenance Principles
* Update this document when the product scope or system boundary changes.
* Update this document when the product range or system boundaries change.
* Update [System Architecture](./architecture.md) when the system structure or module responsibilities change.
* Update [Release Model](./release-model.md) when the release, synchronization, or rollback model changes.
* Update [Development Constraints](./development.md) when development constraints, code specifications, or interface conventions change.
* Update [Deployment Guide](../guide/deployment.md) and README when deployment methods change.
* Update [Configuration Reference](../reference/configuration.md) when configuration items change.
* Completed phases will no longer be backfilled in the form of "version plans".
* Before starting a new phase, complete the design first, then enter implementation.
* Update [Agent & Publish Model](./agent-design.md) when the publishing, synchronization, rollback, or Agent model changes.
* Update [Development Constraints](../../guildline/development-constraints.md) when developer constraints, code specifications, or API conventions change.
* Update README and [Deployment Instructions](../../deployment/deployment.md) when deployment methods change.
* Update [Configurations Reference](../reference/configuration.md) when configuration items change.
* Completed phases should no longer be backfilled as "version plans".
* Before starting a new phase, complement the design first, then proceed to implementation.
-73
View File
@@ -1,73 +0,0 @@
# Release Model
You will learn: Why OpenFlare uses a complete configuration version as the release unit, and how publishing, activation, Agent application, and rollback work.
OpenFlare's release model is centered on complete configuration versions rather than modifying node configurations online.
Standard link:
```text
Modify rules -> Preview / View diff -> Publish -> Generate complete configuration version -> Activate version -> Agent pulls -> Local application -> Report result
```
## Publishing Rules
When publishing, the Server must:
1. Read all enabled `proxy_routes`.
2. Read the Server side OpenResty main configuration template, performance parameters, cache parameters, and necessary Lua resources.
3. Read domain and certificate binding relationships.
4. Read the WAF global rule group, custom rule groups, and website binding relationships.
5. Render the complete OpenResty configuration and WAF runtime configuration.
6. Calculate the `checksum`.
7. Write to `config_versions`.
8. Switch the activated version.
9. Let the Agent discover and apply it in subsequent heartbeats.
The version number format is fixed as `YYYYMMDD-NNN`.
## Preview and Publishing
Preview and diff are read-only capabilities and do not generate release records.
Publishing generates a new complete configuration version. The version must contain sufficient information so that future rollbacks can be re-applied based on historical snapshots, without relying on current mutable configurations.
## Activating Version
There can only be one activated version globally at a time. Differentiated versions grouped by nodes are currently not supported.
The Agent obtains the activated version summary through the heartbeat; only when the remote version or checksum is inconsistent with the local state does the Agent enter the synchronization flow. When Agent WS connection upgrade is enabled and the connection is available, the Server will broadcast the latest active version summary after successfully publishing or activating a version. Upon receiving it, the Agent immediately pulls and applies the configuration using the ordinary synchronization flow. When WS is unavailable, changes are still discovered at HTTP heartbeat intervals.
## Immutable History
Historical versions are immutable. Rollback is not achieved by modifying older versions, but by reactivating older versions.
The result of doing this is:
* Every version can be traced back.
* The rollback link is consistent with the ordinary release application link.
* The Agent does not need to understand "reverse patch", but only needs to apply a target version.
## Agent Application Policy
When discovering a new version, the Agent will:
1. Pull the details of the target version.
2. Back up old files.
3. Write the main configuration, route configurations, certificates, necessary Lua resources, and WAF/PoW runtime configurations.
4. Execute OpenResty configuration verification.
5. reload; if it is not started during runtime, try to start OpenResty with the current configuration.
6. Report success, warning, or failure.
If the activation of the new configuration fails, the Agent must try to restore execution; report a warning when the rollback succeeds. If there is no historical main configuration to roll back to locally, the Agent will write the built-in safe fallback configuration and try to pull up OpenResty: this configuration only listens to port `80` externally, contains no user routes, uniformly returns `503 Service Unavailable` and `OpenFlare: No Valid Configuration`, and retains the local `stub_status` health check entry. If fallback startup is successful, it still blocks the failed target version and reports a warning; report a failure when there is a historical main configuration but it still cannot recover after rollback.
Once a target `version + checksum` application fails and rolls back, the Agent will block repeated applications of this target in its local state. Only when the remote activated version or checksum changes is it allowed to try again.
## Design Constraints
* Publishing must read all enabled site configurations, rather than only rendering the modified object this time.
* Rollback is achieved by reactivating older versions, without modifying historical versions.
* The Agent API is fixed to use the node-exclusive `agent_token`; the first access can use the `discovery_token`.
* The Server does not provide remote shell or arbitrary command execution entries.
* The configuration version must save complete snapshots, rendering results, and `checksum`.
* WAF rule groups and website binding relationships must enter the snapshot and checksum along with the complete configuration version, and must not rely on the current mutable WAF configuration when rolling back.
+93
View File
@@ -0,0 +1,93 @@
# Repository Structure
You will learn: The responsibilities of Server, Agent, Frontend, scripts, and documentation folders in the OpenFlare repository, and where to place logic when contributing code.
| Path | Responsibility |
| --- | --- |
| `openflare_server` | Gin + GORM + SQLite/PostgreSQL single monolithic control plane |
| `openflare_server/web` | Next.js 15 App Router Admin Frontend, hosted by Go Server |
| `openflare_agent` | Go monolithic Agent running on the node side |
| `openflare_relay` | Tunnel relay daemon running on public edges, managing frps processes |
| `openflared` | Tunnel client running on intranet servers, managing frpc processes |
| `scripts` | System helper scripts for installation, self-updating, etc. |
| `docs` | VitePress documentation website, design baselines, specifications, and configurations |
| `docs/en` | English version of documentation |
## Server Layering
| Folder | Responsibility |
| --- | --- |
| `controller/` | Parameter parsing, service calling, and returning responses |
| `service/` | Business logic, validations, transaction orchestration, and configuration rendering |
| `model/` | Model definitions, database versioning, and migrations |
| `router/` | Route registration |
| `middleware/` | Cross-cutting concerns like authentication, authorization, rate limiting, CORS, and Turnstile |
| `common/` | Configurations, global states, and initialization entrypoints |
| `utils/` | Pure utility functions and general helpers |
| `job/` | Periodic cron tasks (such as SSL certificate auto-renewals) |
| `upload/` | File upload handlers |
| `docs/` | API documentation (Swagger) |
| `data/` | Static data (such as GeoIP databases) |
## Agent Modules
| Module | Responsibility |
| --- | --- |
| `config/` | Configuration loading and default values |
| `heartbeat/` | Heartbeat check-in and configuration version evaluation |
| `sync/` | Configuration fetching and application orchestration |
| `nginx/` | OpenResty file writing, validation, reloads, startup, and rollbacks |
| `state/` | Local states and buffers for metric reporting |
| `httpclient/` | Server HTTP API communication |
| `wsclient/` | WebSocket client communication |
| `protocol/` | Agent API protocol types and structures |
| `updater/` | Agent self-updating logic |
| `logging/` | Logging processing |
| `observability/` | Observability (metrics, tracing, etc.) |
| `geoipdata/` | GeoIP database handling |
| `geoipupdate/` | GeoIP database updates |
| `agent/` | Core Agent bootstrap and lifecycle orchestration |
## Frontend Layering
| Folder | Responsibility |
| --- | --- |
| `app/` | Next.js App Router routes, layouts, and page assemblies |
| `features/` | Feature modules organized by business domains |
| `components/` | Reusable UI components shared across features |
| `lib/` | API clients, environment configurations, utility functions, and constants |
| `store/` | Lightweight cross-page UI state management |
| `types/` | Shared TypeScript type definitions |
| `styles/` | Global stylesheets |
| `tests/` | Frontend unit and integration tests (Vitest, Playwright) |
| `scripts/` | Build and deployment scripts |
| `public/` | Static assets |
## Relay Modules
| Module | Responsibility |
| --- | --- |
| `cmd/` | CLI startup entrypoint and main bootstrap functions |
| `internal/config/` | Local configurations parsing and defaults initialization |
| `internal/frps/` | Manages the lifecycle of the frps process, monitoring its status |
| `internal/heartbeat/` | Periodic HTTP heartbeat, status reporting, and update retrievals |
| `internal/httpclient/` | General API client for calling the Server |
| `internal/observability/` | Host and frps metrics collection and pre-aggregation |
| `internal/relay/` | Coordinates the core Relay lifecycle, setup, and cleanup |
| `internal/state/` | Local runtime states, error logs, and persistent caches |
| `internal/updater/` | Relay update check, download installation, and restarts |
| `internal/wsclient/` | Bi-directional real-time WebSocket connection to the Server |
## OpenFlared (Client) Modules
| Module | Responsibility |
| --- | --- |
| `cmd/` | CLI startup entrypoint and main bootstrap functions |
| `internal/config/` | Local client configurations loading and parsing |
| `internal/flared/` | Core client scheduling and tunnel lifecycle orchestration |
| `internal/frpc/` | Dynamically generates `frpc.toml` configs for multiple Relays and monitors frpc processes |
| `internal/heartbeat/` | Heartbeat communications with control planes, including token checks |
| `internal/httpclient/` | General API client for Server communication |
| `internal/sync/` | Incrementally pulls latest Tunnel route bindings, generates snapshots, and applies them |
| `internal/updater/` | Client self-update, new version check, and upgrade installation |
| `internal/wsclient/` | Bi-directional WebSocket client for real-time tunnel configuration pushes |
+130
View File
@@ -0,0 +1,130 @@
# Intranet Penetration Tunnel Design Document
You will learn: The architectural design of the OpenFlare intranet penetration tunnel, the internal principles of the dual-ended control components (Relay and Client), their interaction logics, and the communication flows for the data plane and control plane.
---
## Requirements Analysis
In typical web application hosting scenarios, many origin servers (Origin Servers) are deployed in local intranet environments (such as local development machines, LAN servers, or firewalled private clusters). These servers typically suffer from:
1. **No Public IP**: Cannot be directly accessed by public internet traffic.
2. **Security Compliance Restrictions**: Creating port mappings (NAT) on border routers is strictly prohibited by security policies.
3. **Dynamic IP Changes**: Traditional DDNS solutions exhibit high latency and are highly unstable.
To allow internal origin servers to seamlessly integrate into the OpenFlare global data gateway, benefiting from premium features like WAF geographic protection and TLS certificate hosting, OpenFlare designed an end-to-end solution based on a **reverse relay penetration tunnel**. In this architecture, public edge nodes act as reverse proxy entrances and traffic relays, while the intranet side only needs to initiate secure outbound connections to achieve secure and stable reverse penetration of public traffic to internal origin servers.
---
## Core Capabilities
The intranet penetration tunnel subsystem includes the following core capabilities:
* **Dynamic Relay Node Management**: The control plane dynamically dispatches relay services (frps), distributing service ports and authentication tokens dynamically.
* **Multi-Tunnel Reverse Proxy Mapping**: Supports mapping multiple internal web ports on a single intranet client, binding multiple domain routes to corresponding relay nodes.
* **Independent Process Lifecycle Control**: Both the relay and client are independent daemon processes written in Go, responsible for spawning, monitoring, self-healing, and hot-upgrading the underlying frp engine.
* **Token-based Independent Authentication**: The relay uses `agent_token` for authorization, whereas the intranet client uses its dedicated `tunnel_token`, enforcing isolation of permissions and routing boundaries.
* **Validation & Incremental Hot Reload**: Config files are rewritten and processes are gracefully reloaded only when tunnel bindings, certificates, or Relay topologies change, reducing runtime overhead.
---
## Intranet Penetration & Tunnel Architecture
The intranet penetration subsystem is integrated on top of the mature and high-performance `frp` tunnel protocol, divided into the **Control Plane** and the **Data Plane**.
```mermaid
graph TD
%% Data Flow
Browser[1. Browser / Visitor] -->|HTTPS Request| Agent[2. OpenResty / Agent]
Agent -->|Local proxy_pass| RelayFrps[3. OpenFlare Relay / frps]
RelayFrps -->|Encrypted Tunnel Protocol| FlaredFrpc[4. OpenFlared / frpc]
FlaredFrpc -->|Forward Local Request| LocalOrigin[5. Intranet Origin 192.168.x.x]
%% Control Flow & Heartbeats
Server[OpenFlare Server Control Plane] <-->|Relay API / Heartbeat| RelayManager[openflare_relay process]
Server <-->|Client API / Heartbeat| ClientManager[openflared process]
RelayManager -.->|Control Process & Config| RelayFrps
ClientManager -.->|Control Multi-Relay Processes| FlaredFrpc
style Browser fill:#f9f,stroke:#333,stroke-width:2px
style LocalOrigin fill:#9f9,stroke:#333,stroke-width:2px
style Server fill:#f96,stroke:#333,stroke-width:2px
```
* **Control Plane**: The Server maintains the database state. The `openflare_relay` process on relay nodes and the `openflared` process on intranet servers synchronize tunnel configurations via HTTP heartbeats and long-lived WebSocket connections.
* **Data Plane**: Public traffic enters the public edge Agent (OpenResty), where the TLS handshake, HTTPS termination, and WAF filtering are executed. It is then forwarded via `proxy_pass` to the co-located `openflare_relay (frps)` on the loopback address. `frps` encapsulates the HTTP requests into the encrypted TCP tunnel and sends them down to the intranet `openflared (frpc)`. Finally, `frpc` unpacks the requests and forwards them to the actual intranet origin service.
---
## Relay (Server-side) Design
`openflare_relay` is a relay manager deployed on the public edge, running on nodes of type `tunnel_relay`.
### 1. Core Architecture & Logic
* **Process Daemon**: The Relay process embeds the `frps` binary, spawning the `frps -c frps.toml` subprocess via `exec.Command` and using goroutines to asynchronously listen to its exit status. If `frps` exits unexpectedly, it automatically restarts using an exponential backoff policy.
* **Dynamic Configuration Rendering**: Periodically synchronizes status with the control plane via HTTP heartbeats to retrieve the active `RelayConfig`, including:
* `bindPort`: The public control port that frps listens to for incoming intranet frpc connections.
* `vhostHTTPPort`: The virtual host HTTP listening port where the Agent's proxy_pass points.
* `authToken`: The security credential used during the client connection handshake.
* `webServer`: Enables the frps dashboard API, which the Relay queries to collect active tunnel counts and traffic metrics.
* **Status Reporting**: In each heartbeat cycle, the Relay reports the active connections, registered clients, individual proxy tunnel statuses, and Relay version back to the Server.
---
## Openflared (Client-side) Design
`openflared` is the client manager running inside the user's intranet server, authenticated using a dedicated `tunnel_token`.
### 1. Core Design Mechanisms
* **Multi-Relay Support (Multiplexing)**:
To guarantee high availability and geographical proximity, the control plane may schedule the client to connect to multiple public Relays. `openflared` parses the list of Relays dispatched in the `TunnelConfig`, generating dedicated configurations (`frpc_<relay_node_id>.toml`) and allocating distinct cancelable contexts for each Relay process locally.
* **Independent Subprocess Monitoring**:
`openflared` maintains a local `processes` map to manage the lifecycles of individual `frpc` subprocesses. When the control plane adds or removes Relays, the client incrementally spawns new processes or gracefully shuts down obsolete ones without affecting other functioning tunnels.
* **Dynamic TOML Generation**:
When rendering TOML configs for each Relay, the client iterates over the Proxies list, writing each intranet service's `LocalAddr`, `LocalPort`, and bound `CustomDomains` into standard `[[proxies]]` blocks.
---
## Interaction Logic & Traffic Model
The intranet penetration subsystem implements consistent version control and status feedback loops.
### 1. Control Plane Publishing & Sync Flow
```text
Admin modifies tunnel/intranet port mappings -> Click Publish -> Generate new Tunnel version & Checksum
|
v (Push or Heartbeat Pull)
+-----------------------------------------------------------------------+-----------------------------------------------------------------------+
| |
v (Relay Side) v (Client Side)
openflare_relay heartbeat detects frps port/Token change openflared heartbeat detects tunnel_version change
Re-render local frps.toml Request full proxy configuration details
Kill and restart the frps process Re-render frpc_<relay_id>.toml configs
Report health status as healthy Restart or hot-reload changed frpc processes
Report application results (Apply Success/Error)
```
1. **Versioned Controls**: All intranet tunnel routes and mapping relationships are version-controlled, dispatching a unique `version` and `checksum` to ensure clients do not repeatedly write files or trigger redundant reloads.
2. **Closed-Loop Application Feedback**: After applying new configurations, the client reports the application result in the next heartbeat. If the intranet port is unreachable or certificate bindings fail, the client intercepts the stdout/stderr of the subprocess to report `LastError` to the Server, providing administrators with transparent error details.
### 2. Data Plane Traffic Model
1. **Public Entrance (Agent)**:
```nginx
server {
listen 443 ssl;
server_name intranet.example.com;
# ... TLS certificates & WAF filtering ...
location / {
proxy_pass http://127.0.0.1:18080; # Points to local frps vhost port
proxy_set_header Host $host; # Must preserve the original Host header, which frps relies on to route requests
proxy_set_header X-Real-IP $remote_addr;
}
}
```
2. **Relay Node (frps)**:
`frps` listens to the Vhost port `18080`. When an HTTP request arrives, it extracts `Host: intranet.example.com` from the request headers and searches its active registered tunnel registry to locate the matching encrypted TCP connection (initiated by the intranet frpc).
3. **Encrypted Tunnel Transmission (TCP)**:
`frps` encapsulates the HTTP request into the custom TCP tunnel protocol and transmits it down to the intranet `frpc` client.
4. **Intranet Client Distribution (frpc)**:
The `frpc` instance managed by `openflared` receives the payload, resolves it according to local settings (`localIP = "127.0.0.1"`, `localPort = 8080`), initiates a local TCP connection to forward the request to the intranet web service, and returns the response back through the tunnel to the public viewer.
+132
View File
@@ -0,0 +1,132 @@
# WAF Design Document
You will learn: The core architecture of the OpenFlare edge Web Application Firewall (WAF), the dynamic IP group asynchronous differential sync model, the high-performance OpenResty Lua caching scheme, and the complete request filtering and decision logic.
---
## Requirements Analysis
In public internet environments, web applications face a wide variety of security threats (such as scanner profiling, api scraping, malicious botnets targeted at specific regions, ransomware, and CC attacks). Allowing malicious requests to pass directly to the origin server (Origin Server) results in:
1. **Origin Server Overload**: High-frequency database queries and intensive CPU computations easily exhaust server resources.
2. **Sensitive API Abuse**: APIs like login, registration, and SMS verification codes can be maliciously exploited, leading to financial and computational losses.
3. **Data Exposure Risks**: Malicious common vulnerability probing actions are not intercepted proactively.
Therefore, OpenFlare needs to build a **high-performance, resiliently scalable WAF filtering engine** at the frontmost data plane layer (OpenResty). This engine is capable of executing deep filtering on malicious requests at the edge layer closest to users with sub-millisecond overhead. This relieves pressure on origin servers and provides core security capabilities like CC protection (PoW challenge), IP whitelisting/blacklisting, and region-level interception.
---
## Core Capabilities
OpenFlare WAF includes the following core protection dimensions:
* **IP Interception (IP Whitelist/Blacklist)**: Supports filtering by single IP or CIDR block, and aggregating tens of thousands of IPs into IP groups for highly efficient matching.
* **Geographical Whitelist/Blacklist (GeoIP Limit)**: Integrates MaxMind databases to support precise admission controls based on countries and provinces/regions.
* **Custom Interception Responses**: Supports custom block status codes (e.g., 403, 418) and personalized HTML block pages for different filtering rules.
* **Human-Machine Challenge (PoW CC Protection)**: Supports seamless client-side PoW challenges, calculating Hash collisions to prevent automated scripts and botnets from hitting endpoints concurrently.
---
## IP Group Design & Dynamic Asynchronous Sync
IP groups are the core containers for highly efficient IP whitelisting and blacklisting. OpenFlare classifies IP groups into three types based on their update frequencies and source channels:
### 1. IP Group Types
* **Manual**: Manually input by administrators in the control panel. Primarily used for static trusted IPs or long-term blocks.
* **Subscription**: Configured with remote text feeds (one IP/CIDR per line) or standard JSON subscription URLs. Server-side cron jobs periodically fetch and parse the remote subscription sources. Primarily used for integrating open-source threat intelligence feeds, cloud provider IP ranges, etc.
* **Automatic**: **The most resilient dynamic protection channel**. Control plane scanning jobs read access logs from all nodes, performing aggregation and analysis based on configured Expr rules (e.g., "requesting the `/api/login` endpoint over 50 times with a 401 status code in 5 minutes"). Once matched, the source IP is automatically added to a temporary block list for a specified duration.
### 2. Asynchronous Differential Sync Design (No Nginx Reload)
In traditional Nginx WAF designs, IP blacklist updates typically require writing configurations and executing reloads. If malicious IP blocks occur at high frequencies (seconds or minutes), frequent reloads force Nginx to constantly spawn new worker processes and tear down old ones, severely degrading performance.
OpenFlare adopts a **dynamic IP group asynchronous differential sync design**:
```text
WAF IP member updates (Manual/Subscription/Auto-trigger)
|
v
Server updates the database and calculates the new MD5 Checksum of the IP group
|
+----------------------------------------+
| (WebSocket Real-time Broadcast) | (Heartbeat Fallback Comparison)
v v
Server immediately pushes complete members Agent heartbeats report the local IP groups
of modified groups to all Agents checksum mapping table
| |
| v
| Server detects Checksum mismatch and dispatches
v the modified IP group members
Agent receives member data and writes it as JSON to local disk: waf_ip_groups.json
|
v (Lua Memory Awareness)
OpenResty Lua engine detects file changes via MD5 checksum in seconds and hot-updates its memory,
completely bypassing Nginx process reloads.
```
Through this architecture, the persistence and activation of tens of thousands of highly volatile dynamic blacklist IPs **require absolutely no Nginx reloads**, maximally protecting the high-concurrency throughput of the gateway.
---
## Rule Groups & Site Bindings
* **WAF Rule Group**: The smallest logical collection of WAF filtering policies. A single rule group can contain IP whitelists/blacklists, IP group references, regional restrictions, and CC protection configurations.
* **Global Rule Group**: When a rule group is marked as `is_global = true`, it takes effect on **all website routes** hosted on the node by default.
* **Site Binding**: Website routes (`proxy_routes`) can bind one or more non-global rule groups. During request validation, WAF evaluates the union of `Global Rule Group + Bound Rule Groups`.
---
## Implementation Details & High-Performance Caching
WAF is triggered in the OpenResty `access_by_lua` phase, implemented primarily through Lua files and local JSON configurations.
### 1. Physical Structures
* `waf_config.json`: Contains metadata for all rule groups, geographic country/region limits, and website-to-rule-group bindings.
* `waf_ip_groups.json`: Contains all synchronized IP groups and their corresponding IP lists.
* `waf/runtime.lua`: The actual runtime engine responsible for WAF rule comparison.
* `waf/check.lua`: The entry point for the access layer, handling packages inclusion and triggering `check()`.
### 2. Shared Memory Dictionary (ngx.shared) High-Performance Cache Design
Reading JSON files from the disk and decoding them upon every incoming web request would make disk I/O a severe performance bottleneck.
OpenFlare leverages the **OpenResty Shared Memory Dictionary (ngx.shared.openflare_waf_config)** to implement a two-level caching mechanism:
1. **Zero File I/O Path**:
In Lua, every time `check()` executes, it first computes the MD5 hash of the local JSON file using `ngx.md5` (which takes virtually zero time since the file is cached in the OS Page Cache).
2. **Hash Comparison & Hot Loading**:
It compares this against the cached hash key (`_config_hash`) stored in the shared memory dictionary.
* **If the hash is unchanged**: It reads the pre-decoded Lua Table configuration stored directly in shared memory. The entire verification runs purely in **shared memory**, completing in **microseconds**.
* **If the hash is mismatched**: Indicating that the Agent has just updated the WAF rules or IP groups on the disk, the Lua engine automatically reads the disk file, decodes it via `cjson.decode`, writes the decoded data and the new MD5 hash into shared memory, and makes it seamlessly readable by all subsequent worker processes.
---
## Application Flow & Decision Judgment Control Logic
When an HTTP/HTTPS request arrives at OpenResty, WAF evaluates and intercepts it step-by-step in the `access` phase according to the funnel decision chain below:
### 1. WAF Decision Flowchart
```mermaid
flowchart TD
A[Request enters access phase] --> B[Get Site Name of current request]
B --> C[Load all active rule groups bound to this Site in shared memory]
C --> D{Matches IP whitelist or Whitelist IP group?}
D -- Yes (Matched) --> E[Pass request - ALLOW]
D -- No --> F{Matches country/region whitelist?}
F -- Yes (Matched) --> E
F -- No --> G{Matches IP blacklist or Blacklist IP group?}
G -- Yes (Matched) --> H[Block request - BLOCK]
G -- No --> I{Matches country/region blacklist?}
I -- Yes (Matched) --> H
I -- No --> J{Is CC PoW verification enabled?}
J -- Yes --> K[Transfer to CC Protection module]
J -- No --> L[No security risks, pass normally]
H --> M[Exit and return custom status code and block page HTML configured in the rule group]
```
### 2. Decision Step Details
1. **Whitelist Precedence**:
To prevent false positives and guarantee smooth passage of core back-to-source traffic (such as search engine spiders, CDN back-to-source IPs, and office egresses), WAF **prioritizes matching IP whitelists and regional whitelists**. Once a whitelist matches, it immediately bypasses all subsequent blacklist checks and CC challenges.
2. **Blacklist Aggressive Block**:
If a request is not captured by the whitelist evaluation, it enters the blacklist funnel. Once the source IP matches an IP blacklist, a referenced blacklist IP group, or lies within a prohibited country/region, the Lua engine immediately marks `ngx.ctx.openflare_waf_blocked` as `true`.
3. **Response Output**:
Upon hitting the blacklist, Lua extracts the `block_status_code` (defaults to 418 or 403) and `block_response_body` (interception HTML page) configured in the matching rule group. It outputs the response body via `ngx.say()` and gracefully terminates the request using `ngx.exit(status)` to prevent the request from passing upstream.