You've already forked wp-prometheus
149 lines
4.0 KiB
PHP
149 lines
4.0 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Metrics endpoint class.
|
||
|
|
*
|
||
|
|
* @package WP_Prometheus
|
||
|
|
*/
|
||
|
|
|
||
|
|
namespace Magdev\WpPrometheus\Endpoint;
|
||
|
|
|
||
|
|
use Magdev\WpPrometheus\Metrics\Collector;
|
||
|
|
|
||
|
|
// Prevent direct file access.
|
||
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* MetricsEndpoint class.
|
||
|
|
*
|
||
|
|
* Provides the /metrics endpoint for Prometheus scraping.
|
||
|
|
*/
|
||
|
|
class MetricsEndpoint {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Metrics collector instance.
|
||
|
|
*
|
||
|
|
* @var Collector
|
||
|
|
*/
|
||
|
|
private Collector $collector;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Constructor.
|
||
|
|
*
|
||
|
|
* @param Collector $collector Metrics collector instance.
|
||
|
|
*/
|
||
|
|
public function __construct( Collector $collector ) {
|
||
|
|
$this->collector = $collector;
|
||
|
|
$this->init_hooks();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Initialize WordPress hooks.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private function init_hooks(): void {
|
||
|
|
add_action( 'init', array( $this, 'register_endpoint' ) );
|
||
|
|
add_action( 'template_redirect', array( $this, 'handle_request' ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register the metrics endpoint rewrite rule.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public function register_endpoint(): void {
|
||
|
|
add_rewrite_rule(
|
||
|
|
'^metrics/?$',
|
||
|
|
'index.php?wp_prometheus_metrics=1',
|
||
|
|
'top'
|
||
|
|
);
|
||
|
|
|
||
|
|
add_rewrite_tag( '%wp_prometheus_metrics%', '([^&]+)' );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Handle the metrics endpoint request.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public function handle_request(): void {
|
||
|
|
if ( ! get_query_var( 'wp_prometheus_metrics' ) ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Authenticate the request.
|
||
|
|
if ( ! $this->authenticate() ) {
|
||
|
|
status_header( 401 );
|
||
|
|
header( 'WWW-Authenticate: Bearer realm="WP Prometheus Metrics"' );
|
||
|
|
header( 'Content-Type: text/plain; charset=utf-8' );
|
||
|
|
echo 'Unauthorized';
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Output metrics.
|
||
|
|
status_header( 200 );
|
||
|
|
header( 'Content-Type: text/plain; version=0.0.4; charset=utf-8' );
|
||
|
|
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
|
||
|
|
header( 'Pragma: no-cache' );
|
||
|
|
header( 'Expires: 0' );
|
||
|
|
|
||
|
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Prometheus format.
|
||
|
|
echo $this->collector->render();
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Authenticate the metrics request.
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
private function authenticate(): bool {
|
||
|
|
$auth_token = get_option( 'wp_prometheus_auth_token', '' );
|
||
|
|
|
||
|
|
// If no token is set, deny access.
|
||
|
|
if ( empty( $auth_token ) ) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for Bearer token in Authorization header.
|
||
|
|
$auth_header = $this->get_authorization_header();
|
||
|
|
if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) {
|
||
|
|
return hash_equals( $auth_token, $matches[1] );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for token in query parameter (less secure but useful for testing).
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check.
|
||
|
|
if ( isset( $_GET['token'] ) && hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) ) ) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the Authorization header from the request.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
private function get_authorization_header(): string {
|
||
|
|
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
|
||
|
|
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
|
||
|
|
return sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( function_exists( 'apache_request_headers' ) ) {
|
||
|
|
$headers = apache_request_headers();
|
||
|
|
if ( isset( $headers['Authorization'] ) ) {
|
||
|
|
return sanitize_text_field( $headers['Authorization'] );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
}
|