Files
TheWitnessandThomas Vincent 629971cc73 Harden RRDtool proxy request handling (#20)
* Harden RRDtool proxy request handling

* Address PR review feedback: encrypt oversized-request errors, fix env inheritance, spacing, and symlink test

Addresses review comments on PR #17:
- lib/client.php: encrypt the 'Request too large' error frame once the
  client is authenticated, matching the existing encrypted-error pattern
- lib/client.php: build the removespikes child-process environment from
  getenv() instead of $_ENV so the full current environment (e.g. TEMP on
  Windows) is inherited, with RRDP_RRDTOOL_PATH merged on top
- lib/client.php: add the missing space in the RRDTOOL_PIPE_UNAVAILABLE
  error frame so it matches the other 'ERROR: ...' responses
- tests/run.php: check the symlink() fixture return value and skip the
  symlink-escape assertion (instead of producing a false positive) when
  symlink() is unavailable

* Upgrade phpseclib 3.x to phpseclib/phpseclib ^4.0

- composer.json/composer.lock: require phpseclib/phpseclib ^4.0 (was ^3.0)
- Update phpseclib3\\... namespace usages to phpseclib4\\... across
  rrdtool-proxy.php, lib/client.php, lib/functions.php, lib/wizard.php,
  and tests/run.php
- lib/functions.php: phpseclib4 removed the Random class, so
  Random::string(32) is replaced with random_bytes(32)
- Fix lib/__master__.php and lib/replicator.php, which PR #17 never
  migrated off the old pre-Composer phpseclib API (new \\phpseclib\\Crypt\\RSA(),
  ->loadKey(), ->getPublicKeyFingerprint() -- none of which exist in
  phpseclib 3.x or 4.x, so these MSR/replicator peer-authentication code
  paths would fatal at runtime). Both now use
  RSA::loadPublicKey()->getFingerprint('md5') wrapped in try/catch,
  matching the pattern already used in lib/client.php, plus a
  constant-time hash_equals() fingerprint comparison
- tests/run.php: relabel the phpseclib round-trip check from 'phpseclib 3'
  to 'phpseclib 4'

* Bump minimum PHP version to 8.2 and add CHANGELOG.md

- composer.json: require php ^8.2 (was ^8.1)
- include/global.php: bump RRDP_PHP_VERSION_REQUIRED to 80200 to match
- README.md: update minimum/recommended PHP version to 8.2
- composer.lock: refreshed for the new php platform constraint
- Add CHANGELOG.md tracking changes prefixed security:/issue:/feature:

* Add PHPDoc blocks to all functions

Adds a PHPDoc block above every function in rrdtool-proxy.php,
lib/functions.php, lib/client.php, lib/__master__.php, lib/replicator.php,
lib/wizard.php, cli/removespikes.php, tests/run.php, and
.phpstan-bootstrap.php, each with a brief description and inferred
@param/@return types. Function signatures themselves are unchanged; PHPStan
will flag any inferred types that turn out to be inaccurate.

* Add .github/copilot-instructions.md

Generated from README.md and this session's learnings: project architecture
(daemon entrypoint, shared lib helpers, client/master/replicator roles,
wizard, removespikes CLI), build/lint/test commands (composer scripts,
php tests/run.php, Windows platform-req caveats), and conventions (tabs,
GPL header block, PHPDoc-only typing, phpseclib4 gotchas, CHANGELOG entry
prefixes).

Also fix .gitignore: the '.git*' pattern was unintentionally also matching
and ignoring the .github/ directory; added a '!.github/' exception so files
under .github/ (like this one) are tracked.

* Add .gitattributes enforcing LF line endings; normalize existing CRLF files

- Add .gitattributes with '* text=auto eol=lf' so Git normalizes line
  endings to LF on checkout/commit regardless of local core.autocrlf
  settings.
- .gitignore: add '!.gitattributes' alongside the existing '!.github/'
  exception, since '.git*' was also unintentionally ignoring .gitattributes
  itself.
- Normalize the two tracked files that had CRLF line endings
  (.github/copilot-instructions.md, CHANGELOG.md) to LF; no other tracked
  files were affected.

* Manual PSR coding-style adjustments

Manual formatting/coding-style cleanup in cli/removespikes.php,
lib/__master__.php, lib/client.php, and lib/replicator.php.

* Address automated review feedback: symlink confinement, key-pair validation, and misc fixes

Addresses 10 unresolved copilot-pull-request-reviewer comments on PR #20:

- lib/functions.php: rewrite rrdp_resolve_path_within() to walk the path one
  segment at a time (instead of only checking the nearest existing ancestor
  via file_exists()), so a symlink anywhere along a not-yet-fully-existing
  path is resolved and confined even if the symlink itself is dangling.
- lib/functions.php: add rrdp_resolve_command_paths(), which resolves every
  RRD file path operand referenced by an RRDtool command (leading bare file
  arguments and DEF:/SDEF: clauses) against the RRA root and rewrites the
  command to use the canonical resolved paths, closing the symlink-escape
  gap left by the lexical-only rrdp_command_has_unsafe_path() check.
- lib/client.php: wire rrdp_resolve_command_paths() into the RRDtool command
  dispatch path, rejecting/rewriting unsafe operands before they reach the
  RRDtool pipe.
- lib/client.php: enforce the request-size limit on each completed
  transaction and the trailing unfinished remainder, instead of on the
  aggregate receive buffer before splitting on _EOT_, so multiple legitimate
  transactions in one read no longer trip a false 'Request too large'.
- rrdtool-proxy.php: validate at startup that the loaded public/private key
  files actually form a matching pair (comparing derived fingerprints)
  instead of only deriving a fingerprint from the public key, failing
  startup instead of accepting requests that will all fail to decrypt.
- lib/__master__.php, lib/replicator.php: resolve peer-supplied FULLSCAN
  folder/file names (and RRDLAST/RRDDUMP request filenames) through
  rrdp_resolve_rra_path() before using them in mkdir()/touch()/
  file_put_contents()/RRDtool restore-dump-last-flushcached operations,
  instead of concatenating raw peer-supplied text; reject and skip entries
  that fail confinement.
- lib/replicator.php, lib/__master__.php: guard rrdtool_pipe_init() failures
  in the main replication loop the same way lib/client.php already does,
  instead of indexing into a false return value and never retrying.
- cli/removespikes.php: make createRRDFileFromXML() return whether the
  RRDtool restore actually succeeded, and exit non-zero when it fails
  instead of silently exiting 0.
- .phpstan.neon: restore level 6 (was lowered to 2, contradicting
  .github/copilot-instructions.md) and add .phpstan-baseline.neon to
  capture the 42 pre-existing findings surfaced at level 6 without hiding
  future regressions.
- tests/run.php: add the standard Cacti Group GPL header block required for
  every PHP file.

---------

Co-authored-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-19 09:04:05 -04:00

211 lines
7.1 KiB
Markdown

# rrdproxy
Cacti RRDtool Proxy Server is a standalone proxy interface to Tobi Oetiker's
great RRDtool.
The main focus of this project is to allow the relocation of RRD files, data
replication, support of Cacti Boost and, if needed, the RRDcached daemon. It
allows system administrators to split Cacti's access resources across machines
without the need for NFS.
Because updates may contain sensitive data, RRDtool Proxy encrypts client
traffic using RSA-2048 key exchange and a fresh 256-bit symmetric key for each
message. Client IP addresses and public-key fingerprints must also be explicitly
allowlisted.
The current compatibility protocol verifies the presented public-key
fingerprint, but does not yet perform a signed challenge proving possession of
the corresponding private key. Its legacy CBC message format also lacks an
authenticated integrity tag. Deploy the client port only on a trusted,
firewalled management network. A future protocol version must add private-key
proof and authenticated encryption in coordination with the Cacti client
before these limitations can be removed.
A local service port is provided to allow administrators are able to access a
separate command line interface which provides easy access to configure and
debug the proxy.
## Contribute
Get involved in development by participating in active development on
[GitHub](https://github.com/Cacti/rrdproxy/).
## Requirements
RRDtool Proxy Server runs on any Unix-based operating system that can meet the
minimum requirements, though it will perform better with the recommended (or
higher). The most recent version of RRDProxy requires composer
### Minimum Requirements
- PHP Version 8.2 or higher
Installation must include and enable the following modules:
- Sockets
- Posix
- PCntl
- GMP
- ZLIB
- RRDtool Version 1.5 or higher
- Cacti Version 1.2.31 or higher
### Recommended Requirements
All minimum requirements must be met, though the following are more optional:
- PHP Version 8.2 or higher
Include all minimum recommendation modules
- RRDtool 1.7 or higher
- Cacti 1.2.7
## Usage
Before using the RRDtool Proxy Server, you must install the system using the
setup wizard which can be run at any point using the -w parameter.
```shell
composer install --no-dev
php rrdtool-proxy.php --help
RRDtool Proxy v1.2.17
Copyright (C) 2004-2026 The Cacti Group
usage: rrdtool-proxy.php [--wizard] [-w] [--version] [-v]
Optional:
-v --version - Display this help message
-w --wizard - Start Configuration Wizard
```
When running through the wizard, the status of items will appear on the right
hand side of the screen. When an item is successfully completed, the status of
`[OK]` will be shown in green (when color is supported) as seen below:
```shell
Checking System Requirements...
[0.00001] test: operation system supported [OK]
[0.00007] test: php module 'sockets' [OK]
[0.00011] test: php module 'posix' [OK]
```
Should any item have an issue, the status will be displayed as something other
than `[OK]` such as `[FAILED]`:
```shell
[0.40270] read: RRDproxy configuration file [FAILED]
```
Status Color | Description
: --- | : ---
Green | Success and without issue
Yellow | Warnings and will be ignored, correct the problem if needed
Red | Errors and will not be ignored, may prevent continuation
When the wizard prompts for the various settings that are available, you can
accept the default value simply by pressing return to accept it. Below are a few
of these settings and the default values that can be accepted:
Setting | Default | Comment
: --- | : --- | : ---
CLI System Prompt | rrdp | Max 8 chars
IPv4 Listen Addr | 0.0.0.0 | Listen all addr
Client Proxy Port | 40301 | Connections from a client
Proxy Proxy port | 40302 | Connections from another proxy
Dedicated CLI port | 40303 | Connections to the proxy cli
If the wizard displays a prompt that requires a response and it has no default,
you will be prompted again until a valid response has been entered.
## Command Line Interface
Use '?' or 'help' to get a list of all commands being supported by the proxy or
hints about missing parameters. The following shows an example of using the CLI
to list the available commands to find the version of RRDtool Proxy Server.
Note: that most commands require that the CLI is in privileged mode and this
password is encrypted in the configuration so cannot be guessed. If the
password has been forgotten, it can be reset using the RRDtool Proxy
Server Wizard but the background process will need to be manually terminated.
```
telnet localhost 40303
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
rrdp>?
enable Turn on privileged commands
help Display help
show Show running system information
quit Close terminal session
rrdp>ena Tester12
rrdp# help
clear Clear functions for internal buffers
disable Turn off privileged commands
reset Reset terminal screen
set Configure proxy settings
show Show running system information
shutdown Close all connections and shut down proxy
quit Close terminal session
rrdp# show ?
clients List all trusted client connections
counters Return all system counters
cluster List all trusted cluster peers
logging Display state of logging
msr Display replication state
processes Display running child processes
rsa Show Encryption setup
threads Display currently open connections
variables Show Current Operating configuration
version System software status
rrdp#sh version
# ___ _ _ __ __ ___ ___
# / __\__ _ ___| |_(_) /__\ /__\ / \ / _ \_ __ _____ ___ _
# / / / _` |/ __| __| | / \// / \// / /\ / / /_)/ '__/ _ \ \/ / | | |
# / /__| (_| | (__| |_| | / _ \/ _ \/ /_// / ___/| | | (_) > <| |_| |
# \____/\__,_|\___|\__|_| \/ \_/\/ \_/___,' \/ |_| \___/_/\_\__, |
# |___/
RRDtool Proxy v1.2.17
Copyright (C) 2004-2026 The Cacti Group
rrdp uptime is 14 days, 0 hours, 4 minutes, 14 seconds
Memory usage 0.18872917 % (2026464/1073741824 in bytes)
a8:29:6a:b7:5d:be:c3:8a:be:13:7a:61:ee:0c:8b:d3
Process ID: 13996
Session usage (1/399)
Server IP [192.168.10.10]
Administration: [localhost :40303]
Replication: [192.168.10.10 :40302]
Clients: [192.168.10.10 :40301]
rrdp#
```
## Planned features
- Data replication
This is currently being written and has been mostly completed, but it is still
being tested and so hasn't been made available yet.
- Proxy stats via SNMP
A NET-SNMP persistent daemon and MIB needs to written to enabled support of
proxy stats via SNMP. This could lead to an interesting loop of Cacti storing
stats for RRDproxy within RRDproxy.
- International language support
Currently, the RRDproxy wizard and command line interface port are both
offering only english as the default language.