v0.25.0
This commit is contained in:
1 parent
2e4aa4df4d
commit
87e571c170
106 files changed
+17952
-6185
No files matched your search
+225
@@ -0,0 +1,225 @@
|
||||
# PHP-FPM / FastCGI for the Web Server
|
||||
|
||||
RustProxy 0.25 can execute configured PHP extensions through a long-running PHP-FPM pool while preserving the reusable Web Service architecture.
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
Request
|
||||
-> existing listener / vhost / location / ACL / auth
|
||||
-> Web Server -> Web Service
|
||||
|- static file
|
||||
`- configured PHP extension -> FastCGI -> PHP-FPM
|
||||
-> existing error/header/logging/metrics/transport pipeline
|
||||
```
|
||||
|
||||
Vhosts and locations still reference only a Web Service. PHP settings remain part of that reusable service.
|
||||
|
||||
## Runtime model
|
||||
|
||||
RustProxy implements the FastCGI protocol directly. It does not spawn `php`, `php-cgi` or shell processes for individual requests.
|
||||
|
||||
Supported endpoint syntax:
|
||||
|
||||
```text
|
||||
unix:/run/php/php-fpm.sock
|
||||
tcp:127.0.0.1:9000
|
||||
tcp:[::1]:9000
|
||||
```
|
||||
|
||||
Use the `unix:` prefix for a Unix-domain socket. Use `tcp:` followed by a literal loopback IP address and port for TCP. Hostnames and non-loopback addresses are rejected. Unix sockets are preferred for a local PHP-FPM installation; exposing FastCGI directly to an untrusted network is not supported by this configuration.
|
||||
|
||||
## Web Service configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"index": ["index.html", "index.php"],
|
||||
"try_files": ["$uri", "$uri/", "/index.php?$query_string"],
|
||||
"php": {
|
||||
"enabled": true,
|
||||
"extensions": ["php"],
|
||||
"endpoint": "unix:/run/php/php-fpm.sock",
|
||||
"connect_timeout_ms": 1000,
|
||||
"request_timeout_ms": 30000,
|
||||
"max_concurrent_requests": 64,
|
||||
"pass_authorization": true,
|
||||
"max_request_body_bytes": 0,
|
||||
"max_response_header_bytes": 65536,
|
||||
"stderr_log_bytes": 4096,
|
||||
"custom_params": {
|
||||
"APP_ENV": "production"
|
||||
},
|
||||
"php_values": {
|
||||
"memory_limit": "256M",
|
||||
"upload_max_filesize": "64M",
|
||||
"post_max_size": "64M"
|
||||
}
|
||||
},
|
||||
"symlink_policy": "deny"
|
||||
}
|
||||
```
|
||||
|
||||
The normal ordered `index` list handles `index.php`; there is no separate PHP-index option.
|
||||
|
||||
### Additional PHP/FastCGI options
|
||||
|
||||
- `pass_authorization`: forwards the inbound `Authorization` header as `HTTP_AUTHORIZATION`. Disable it when the application does not need that header.
|
||||
- `max_request_body_bytes`: streaming request-body limit enforced before/during FastCGI forwarding. `0` means no additional RustProxy-side limit. A request exceeding the configured value receives `413 Payload Too Large`.
|
||||
- `max_response_header_bytes`: maximum bytes accepted before the CGI response-header terminator. The allowed range is 4096..1048576 bytes.
|
||||
- `stderr_log_bytes`: maximum PHP-FPM stderr bytes retained for one diagnostic request entry. `0` disables stderr capture; maximum 65536.
|
||||
- `custom_params`: trusted static FastCGI parameters supplied by the operator.
|
||||
- `php_values`: php.ini-style overrides encoded into one validated FastCGI `PHP_VALUE` parameter.
|
||||
|
||||
### Custom FastCGI parameter syntax
|
||||
|
||||
In the administration UI use one entry per line:
|
||||
|
||||
```text
|
||||
APP_ENV = production
|
||||
APP_DEBUG = 0
|
||||
TENANT = main
|
||||
```
|
||||
|
||||
Names are normalized to uppercase and are limited to letters, digits and `_` (the first character must be a letter). Values are static: there is no request-variable interpolation in this field. RustProxy rejects core CGI parameters, `HTTP_*`, `PHP_VALUE` and `PHP_ADMIN_VALUE`, so custom values cannot replace `SCRIPT_FILENAME`, routing data, HTTP-derived fields or PHP control parameters. Up to 64 custom FastCGI parameters and 64 PHP INI overrides are accepted, with at most 128 KiB of combined custom parameter/value data.
|
||||
|
||||
### PHP INI (`PHP_VALUE`) syntax
|
||||
|
||||
Use one `directive = value` entry per line:
|
||||
|
||||
```text
|
||||
memory_limit = 256M
|
||||
upload_max_filesize = 64M
|
||||
post_max_size = 64M
|
||||
max_execution_time = 30
|
||||
```
|
||||
|
||||
RustProxy validates the lines and combines them with newline separators into the FastCGI `PHP_VALUE` parameter. PHP-FPM/PHP still decides whether a directive is changeable at this scope. `PHP_ADMIN_VALUE` is intentionally not exposed. Control characters/newlines inside names or values are rejected.
|
||||
|
||||
These custom parameters are stored as normal Web Service configuration. Do not put secrets there unless normal configuration storage is an acceptable location for them.
|
||||
|
||||
## `try_files` front controller
|
||||
|
||||
A typical application can use:
|
||||
|
||||
```text
|
||||
$uri
|
||||
$uri/
|
||||
/index.php?$query_string
|
||||
```
|
||||
|
||||
Existing static files/directories are used first. The final path becomes the fallback and can therefore reach `index.php`. This resolution remains inside the already-selected Web Service and does not introduce a second vhost router.
|
||||
|
||||
## Bounded execution
|
||||
|
||||
Each compiled Web Service owns a bounded PHP concurrency semaphore. `max_concurrent_requests` limits simultaneous FastCGI requests. RustProxy does not create an unbounded wait queue: if no execution slot is immediately available it returns `503 Service Unavailable` with `Retry-After`.
|
||||
|
||||
Connection establishment and complete FastCGI request execution have independent configured time limits. A connect failure produces a gateway error; a request timeout produces a gateway timeout. The outer normal RustProxy error-page processing remains able to replace these responses.
|
||||
|
||||
Request bodies are split into bounded FastCGI records rather than collected into one large buffer. When `max_request_body_bytes` is non-zero, the limit is checked both from `Content-Length` when available and while streaming chunked/unknown-length bodies. PHP-FPM stdout is parsed and delivered through a bounded response channel so application output can stream to the client.
|
||||
|
||||
## Secure script selection
|
||||
|
||||
Only configured extensions are executable. Before PHP is selected, RustProxy resolves the request/`try_files`/index path using the Web Server filesystem confinement rules and verifies that it is a regular file below the document root.
|
||||
|
||||
When PHP handling was selected:
|
||||
|
||||
- PHP source is never returned as a fallback;
|
||||
- an unavailable PHP-FPM endpoint produces 502/504 behavior rather than source disclosure;
|
||||
- encoded traversal/separators and `..` remain rejected;
|
||||
- the current implementation requires `symlink_policy=deny`.
|
||||
|
||||
The last restriction is deliberate: PHP-FPM opens `SCRIPT_FILENAME` independently, so RustProxy does not claim an `inside_root` symlink guarantee that the external process cannot enforce atomically.
|
||||
|
||||
## FastCGI parameters
|
||||
|
||||
RustProxy supplies a controlled CGI/FastCGI parameter set including:
|
||||
|
||||
- `REQUEST_METHOD`
|
||||
- `SCRIPT_FILENAME`
|
||||
- `SCRIPT_NAME`
|
||||
- `QUERY_STRING`
|
||||
- `REQUEST_URI`
|
||||
- `DOCUMENT_ROOT`
|
||||
- `SERVER_PROTOCOL`
|
||||
- `SERVER_NAME`
|
||||
- `SERVER_PORT`
|
||||
- `REMOTE_ADDR`
|
||||
- `REMOTE_PORT` when available
|
||||
- `HTTPS`
|
||||
- `GATEWAY_INTERFACE`
|
||||
- `SERVER_SOFTWARE`
|
||||
- `REDIRECT_STATUS`
|
||||
- `CONTENT_TYPE`
|
||||
- `CONTENT_LENGTH`
|
||||
- selected request headers as `HTTP_*`
|
||||
|
||||
Request headers cannot become FastCGI `PHP_VALUE` or `PHP_ADMIN_VALUE` parameters. Operator-defined `custom_params` are allowed only through the validated Web Service configuration and cannot override core CGI/HTTP-derived fields. Operator-defined `php_values` are the only path that creates `PHP_VALUE`; `PHP_ADMIN_VALUE` is never generated.
|
||||
|
||||
An inbound `Proxy` header is **never** translated into FastCGI `HTTP_PROXY` (httpoxy mitigation), regardless of the optional Web Service hardening switch. The **Block common attack probes** option additionally blocks high-confidence sensitive/probe paths before PHP-FPM dispatch. FastCGI output cannot control downstream `Content-Length`, transfer framing, connection headers or `Proxy-*` transport headers; RustProxy owns downstream response framing.
|
||||
|
||||
`PATH_INFO` script/path splitting is not implemented in the current 0.25 development stage; applications should route explicitly to a real script, normally through `try_files`.
|
||||
|
||||
## Response parsing
|
||||
|
||||
The FastCGI stdout parser understands standard CGI response headers, including `Status`. Hop-by-hop transport headers are stripped before returning the response through RustProxy.
|
||||
|
||||
For `HEAD`, PHP-FPM can still execute normally but the client response body is empty. GET/POST/PUT and other methods can reach a PHP script once routing resolves to a configured executable extension.
|
||||
|
||||
PHP stderr is bounded by `stderr_log_bytes` and written to the RustProxy diagnostic log; it is not sent to the client as page content. CGI response headers are separately bounded by `max_response_header_bytes`.
|
||||
|
||||
## Caching
|
||||
|
||||
PHP output does not use static file validators or the static-file OS representation path. Application response headers and the normal vhost/location response-header pipeline control client/intermediary caching.
|
||||
|
||||
The Web Service static `cache_rules` apply to local static file responses, not to PHP stdout. RustProxy does not silently place PHP responses into a new Web Server object cache.
|
||||
|
||||
## PHP installation
|
||||
|
||||
RustProxy does not bundle PHP. Install and maintain PHP-FPM using the operating system/package ecosystem and enable only the extensions required by the application.
|
||||
|
||||
Operational requirements:
|
||||
|
||||
- a running PHP-FPM 8.x service supported by the operator's platform;
|
||||
- a dedicated Unix socket or loopback TCP listener reachable by the RustProxy service account;
|
||||
- document-root permissions usable by both RustProxy and the PHP-FPM worker identity as intended;
|
||||
- correct socket owner/group/mode when using Unix sockets;
|
||||
- PHP-FPM limits such as `pm.max_children`, application execution timeout and upload/body limits sized consistently with the RustProxy concurrency limit.
|
||||
|
||||
Extensions such as PDO drivers, mbstring, intl, gd, curl or opcache are application dependencies, not RustProxy dependencies.
|
||||
|
||||
## Example PHP-FPM pool concept
|
||||
|
||||
The exact paths/users depend on the operating system. For production, use a **separate PHP-FPM worker identity per site/application**. Never run PHP-FPM as `root` or as the `rustproxy` service account. A compromised CMS/plugin then must not inherit access to RustProxy's SQLite database, Secret Store, certificate private keys or backups.
|
||||
|
||||
A local pool can expose a socket such as:
|
||||
|
||||
```ini
|
||||
user = site_example
|
||||
group = site_example
|
||||
listen = /run/php/rustproxy-example.sock
|
||||
listen.owner = www-data
|
||||
listen.group = rustproxy
|
||||
listen.mode = 0660
|
||||
```
|
||||
|
||||
The exact socket owner may differ by distribution; the important properties are that the PHP worker user is **not** `rustproxy`, and only RustProxy plus the pool manager can reach the socket. Configure the Web Service endpoint as:
|
||||
|
||||
```text
|
||||
unix:/run/php/rustproxy-example.sock
|
||||
```
|
||||
|
||||
Keep `/var/lib/rustproxy` mode/private ownership inaccessible to every PHP pool. Prefer separate writable upload/data directories from executable PHP source, and prevent the application from creating executable PHP files in upload areas. A CMS-level arbitrary file upload or vulnerable plugin can otherwise become PHP code execution even when the reverse proxy itself is correct.
|
||||
|
||||
Do not expose a PHP-FPM TCP listener to the public network.
|
||||
|
||||
## Verification targets
|
||||
|
||||
The implementation includes source/unit coverage for:
|
||||
|
||||
- PHP front-controller resolution through `try_files`;
|
||||
- request body delivery to a FastCGI fixture;
|
||||
- FastCGI response parsing;
|
||||
- connection failure returning a gateway error without PHP source disclosure;
|
||||
- bounded endpoint/time/concurrency configuration validation.
|
||||
|
||||
Release-host acceptance should additionally exercise a real PHP-FPM runtime over Unix socket and loopback TCP and verify ACL/auth/custom errors/headers plus H1/H2/H3 client transports.
|
||||
Reference in new issue
Block a user