teufels / tt3-stackmonitor
[teufels] stackmonitor - Secret-protected /stackmonitor endpoint that reports CMS, PHP and database versions as JSON to the teufels stack monitoring dashboard
Package info
bitbucket.org/teufels/tt3-stackmonitor/
Type:typo3-cms-extension
pkg:composer/teufels/tt3-stackmonitor
Requires
- php: ^7.2 || ^8.0
- typo3/cms-core: ^9.5 || ^10.4 || ^11.5 || ^12.4 || ^13.4 || ^14.3
- typo3/cms-frontend: ^9.5 || ^10.4 || ^11.5 || ^12.4 || ^13.4 || ^14.3
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
[ ṯeufels ] Stackmonitor
Minimal monitoring endpoint for the teufels stack monitoring dashboard. GET https://<domain>/stackmonitor returns the CMS, PHP and database versions of the installation as JSON. The data is only handed out if the request carries the shared secret in the HTTP header X-Stackmonitor-Secret.
This version supports TYPO3
TYPO3 9.5 to 14.3 (PHP 7.2 to 8.5), one code base. Verified at runtime on TYPO3 14.3, the APIs used are checked against 9.5 - 13.4.
Composer support
composer req teufels/tt3-stackmonitor
Links
| URL | |
|---|---|
| Repository: | https://bitbucket.org/teufels/tt3-stackmonitor/ |
| Packagist: | https://packagist.org/packages/teufels/tt3-stackmonitor/ |
| TER: | tbd |
| Documentation: | tbd |
1. How it works
A single PSR-15 middleware is registered in the frontend middleware stack in front of the site resolver. It answers requests to /stackmonitor itself and passes everything else through untouched. No page, site configuration, TypoScript or web server rewrite is needed, the dashboard only needs the domain. The path /stackmonitor is fixed and therefore reserved.
2. Installation
composer req teufels/tt3-stackmonitorand set up the extension (typo3 extension:setup). Classic mode: install the extension in the Extension Manager.- Set the secret (see below). Without a secret the endpoint stays closed.
3. Secret
The secret is a single field of the extension configuration (Admin Tools > Settings > Extension Configuration > tt3_stackmonitor). It is stored in settings.php (LocalConfiguration.php before TYPO3 12) under EXTENSIONS/tt3_stackmonitor/secret.
- Format: letters and digits, 43 characters recommended (e.g.
jnadAMFadkerASiONcEImIStivIsHeRATHe21khRMhec), at least 16 required. - Generate one with a password generator or
LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 43; echo. - Enter the identical secret for the domain in the dashboard.
- Replace the secret by entering a new value in the field and updating it in the dashboard.
- Use a separate secret per installation and environment. It may be identical to the
t3monitoring_clientsecret, but the two are configured independently (see below).
Secret from .env
In teufels projects the secret of each environment is set in its .env. Add STACKMONITOR_SECRET there (template: #STACKMONITOR_SECRET="" in .env.local). config/system/additional.php maps it to the extension configuration, next to T3MONITORING_CLIENT_SECRET. A set variable overrides the value from settings.php, without it the extension configuration applies. This works in non-local environments only, on *.localhost the value from settings.php is used.
STACKMONITOR_SECRET and T3MONITORING_CLIENT_SECRET are independent, neither falls back to the other. To use the same secret for both, enter the same value in both variables. t3monitoring_client receives its secret as ?secret= query parameter, which ends up in access logs, a shared secret is exposed the same way. The minimum length of 16 characters applies to the stackmonitor secret, a shorter one keeps the endpoint closed (HTTP 503).
Mapping for projects whose additional.php does not have it yet (inside the non-local branch, after the T3MONITORING_CLIENT_SECRET block):
if (isset( $config['STACKMONITOR_SECRET'] ) && $config['STACKMONITOR_SECRET'] != "" ) {
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['tt3_stackmonitor']['secret'] = $config['STACKMONITOR_SECRET'];
}
Other setups: set $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['tt3_stackmonitor']['secret'] in config/system/additional.php (typo3conf/AdditionalConfiguration.php before TYPO3 12), it is executed after settings.php.
4. Request and response
curl -H "X-Stackmonitor-Secret: <secret>" https://example.com/stackmonitor
200 OK
{
"cms": "TYPO3",
"cmsVersion": "14.3.10",
"phpVersion": "8.5.12",
"dbVersion": "11.8.9-MariaDB-ubu2404-log",
"serverIp": "192.168.2.1"
}
| Key | Source |
|---|---|
cms | fixed value TYPO3 |
cmsVersion | TYPO3 version |
phpVersion | major.minor.patch of the running PHP, without distribution suffix |
dbVersion | raw SELECT VERSION() of the Default database connection |
serverIp | SERVER_ADDR as seen by PHP (fallback: gethostbyname(gethostname())), may be internal behind a proxy |
Errors are always returned as {"error": "<message>"}:
| Status | Case | error |
|---|---|---|
| 405 | method is not GET (response header Allow: GET) | Method not allowed |
| 401 | header X-Stackmonitor-Secret missing or empty | Missing X-Stackmonitor-Secret header |
| 503 | no secret configured (empty or shorter than 16 characters) | Stackmonitor is not configured |
| 401 | secret does not match | Secret does not match |
| 503 | database not reachable | Database not reachable |
All responses carry Cache-Control: no-store, X-Robots-Tag: noindex and X-Content-Type-Options: nosniff.
5. Notes
- Use HTTPS only. The secret is accepted in the header only, a
?secret=query parameter is ignored (it would end up in access logs). - The secret is compared in constant time. Data is only collected after a successful check, unauthorized requests cause no database load.
- Without a valid secret the endpoint never returns data (fail closed).
- The secret is stored in clear text in
settings.php, like the database password. It is visible to backend admins in System > Configuration ifEXT:lowlevelis installed. - No logging and no rate limiting, this is a task for the web server or a WAF.
/stackmonitormust reach TYPO3. The default TYPO3.htaccessand nginx configuration pass unknown paths toindex.php; a blocking WAF, Basic Auth or a rewrite rule that catches the path prevents it.- Web server error pages (e.g. nginx
fastcgi_intercept_errors ontogether witherror_page 401 ...) replace the JSON error of a 4xx/5xx response with their own page, or with a 404 if that page does not exist. The200response is never affected.