mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 23:06:36 +08:00
文档更新
This commit is contained in:
@@ -1,181 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,223 +0,0 @@
|
||||
# System Architecture
|
||||
|
||||
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, 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
|
||||
|
|
||||
| Management UI / API
|
||||
v
|
||||
OpenFlare Server (Gin + GORM + SQLite/PostgreSQL)
|
||||
|
|
||||
| Agent API / heartbeat / config pull
|
||||
v
|
||||
OpenFlare Agent
|
||||
|
|
||||
| write config / openresty -t / reload / rollback
|
||||
v
|
||||
OpenResty binary
|
||||
|
|
||||
| reverse proxy
|
||||
v
|
||||
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 | 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 the single-control-plane monolith:
|
||||
|
||||
* Gin provides the HTTP services.
|
||||
* GORM accesses SQLite or PostgreSQL.
|
||||
* 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 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 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 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.
|
||||
|
||||
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 Next.js-based frontend:
|
||||
|
||||
* Next.js 15 App Router.
|
||||
* React 19.
|
||||
* TypeScript.
|
||||
* Tailwind CSS.
|
||||
* TanStack Query for server-side state.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Data & Request Flow
|
||||
|
||||
### Management Request Flow
|
||||
|
||||
```text
|
||||
Browser -> Frontend -> /api/* -> controller -> service -> model -> database
|
||||
```
|
||||
|
||||
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 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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
```text
|
||||
Client -> OpenResty server block -> WAF Lua -> named upstream -> Origin
|
||||
```
|
||||
|
||||
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 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
|
||||
|
||||
Current valid entities include:
|
||||
|
||||
* `proxy_routes`
|
||||
* `origins`
|
||||
* `config_versions`
|
||||
* `nodes`
|
||||
* `tunnels`
|
||||
* `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_ip_groups`
|
||||
* `waf_rule_group_bindings`
|
||||
* `acme_accounts`
|
||||
* `dns_accounts`
|
||||
* `geoip_update_configs`
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
| Decision | Rationale |
|
||||
| --- | --- |
|
||||
| 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. |
|
||||
|
||||
## Recommended Reading for Contributors
|
||||
|
||||
Before modifying architectural code, please read:
|
||||
|
||||
1. [Product Boundaries](./index.md)
|
||||
2. [Agent & Publish Model](./agent-design.md)
|
||||
3. [Development Constraints](../../guideline/Constraints.md)
|
||||
4. [Repository Structure](./repository.md)
|
||||
@@ -1,182 +0,0 @@
|
||||
# Local Development
|
||||
|
||||
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 page is aimed at contributors. Product boundaries, data model constraints, API conventions, and frontend layering specifications are governed by [Development Constraints](../../guideline/Constraints.md); this page only provides actionable workflows for local development.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
For details on the physical directory structure and responsibilities of each module (Server, Agent, Frontend, etc.), see [Repository Structure](./repository.md).
|
||||
|
||||
## Environment Requirements
|
||||
|
||||
| Item | Requirement |
|
||||
| --- | --- |
|
||||
| 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 |
|
||||
|
||||
## Initializing Frontend Dependencies
|
||||
|
||||
```bash
|
||||
cd openflare-server/web
|
||||
corepack enable
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Build the static assets hosted by the Go Server:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Starting the Server
|
||||
|
||||
SQLite Mode:
|
||||
|
||||
```bash
|
||||
cd openflare-server
|
||||
export SESSION_SECRET='dev-session-secret'
|
||||
export SQLITE_PATH='./openflare-dev.db'
|
||||
export LOG_LEVEL='debug'
|
||||
go run .
|
||||
```
|
||||
|
||||
PostgreSQL Mode:
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
Default access URL:
|
||||
|
||||
```text
|
||||
http://localhost:3000
|
||||
```
|
||||
|
||||
The default credentials are `root` / `123456`.
|
||||
|
||||
## Starting the Frontend Dev Server
|
||||
|
||||
The frontend dev server listens to port `3001` by default and proxies requests to the backend via `NEXT_DEV_BACKEND_URL`:
|
||||
|
||||
```bash
|
||||
cd openflare-server/web
|
||||
export NEXT_DEV_BACKEND_URL='http://127.0.0.1:3000'
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Access:
|
||||
|
||||
```text
|
||||
http://localhost:3001
|
||||
```
|
||||
|
||||
## Starting the Agent
|
||||
|
||||
Create a local `agent.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"server_url": "http://127.0.0.1:3000",
|
||||
"agent_token": "replace-with-node-auth-token",
|
||||
"data_dir": "./data",
|
||||
"heartbeat_interval": 10000,
|
||||
"request_timeout": 10000
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd openflare-agent
|
||||
export LOG_LEVEL='debug'
|
||||
go run ./cmd/agent -config ./agent.json
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
## Running Tests
|
||||
|
||||
Server:
|
||||
|
||||
```bash
|
||||
cd openflare-server
|
||||
GOCACHE=/tmp/openflare-go-cache go test ./...
|
||||
```
|
||||
|
||||
Agent:
|
||||
|
||||
```bash
|
||||
cd openflare-agent
|
||||
GOCACHE=/tmp/openflare-go-cache go test ./...
|
||||
```
|
||||
|
||||
Frontend:
|
||||
|
||||
```bash
|
||||
cd openflare-server/web
|
||||
pnpm lint
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
pnpm test:e2e
|
||||
```
|
||||
|
||||
Docs:
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
Admin static assets:
|
||||
|
||||
```bash
|
||||
cd openflare-server/web
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Server binary:
|
||||
|
||||
```bash
|
||||
cd openflare-server
|
||||
go build -o openflare-server .
|
||||
```
|
||||
|
||||
Agent binary:
|
||||
|
||||
```bash
|
||||
cd openflare-agent
|
||||
go build -o openflare-agent ./cmd/agent
|
||||
```
|
||||
|
||||
## Debugging Entrypoints
|
||||
|
||||
| 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` |
|
||||
|
||||
## Code Style & Change Admission
|
||||
|
||||
Before contributing, verify:
|
||||
|
||||
1. The requirement matches [Product Boundaries](./index.md).
|
||||
2. The implementation conforms to [Development Constraints](../guideline/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.
|
||||
|
||||
Database schema alterations must elevate the database version number and supply explicit migration and validation methods from the previous version.
|
||||
@@ -1,204 +0,0 @@
|
||||
# Product Boundaries
|
||||
|
||||
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 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:
|
||||
|
||||
* 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 logging platform, service mesh, Kubernetes Ingress Controller, or multi-tenant cloud platform.
|
||||
|
||||
## Current Capabilities
|
||||
|
||||
| Capability | Description |
|
||||
| --- | --- |
|
||||
| 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 Model:
|
||||
|
||||
* 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 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. |
|
||||
|
||||
## Website Configuration Constraints
|
||||
|
||||
`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` 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`.
|
||||
* 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 & Upstream Constraints
|
||||
|
||||
`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.
|
||||
|
||||
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.
|
||||
|
||||
## 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 the domain-certificate bindings parallel to `domains`; a value of `0` means the domain does not have HTTPS enabled and stays HTTP-only.
|
||||
|
||||
During rendering:
|
||||
|
||||
* 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 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.
|
||||
|
||||
Core Capabilities:
|
||||
|
||||
* 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`).
|
||||
|
||||
IP Group & Judgment Constraints:
|
||||
|
||||
* **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` 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:
|
||||
|
||||
* 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.
|
||||
|
||||
## Version & Observability Constraints
|
||||
|
||||
* `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 range or system boundaries change.
|
||||
* Update [System Architecture](./architecture.md) when the system structure or module responsibilities change.
|
||||
* Update [Agent & Publish Model](./agent-design.md) when the publishing, synchronization, rollback, or Agent model changes.
|
||||
* Update [Development Constraints](../../guideline/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.
|
||||
@@ -1,93 +0,0 @@
|
||||
# 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 |
|
||||
@@ -1,130 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,132 +0,0 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user