pcp/pcp-7.1.5-pmproxy-logger-auth.patch
Jan Kurik 4d55c1806b
Backport remaining private-pcp security hardening fixes for 7.1.5-6
Add patches for pmieconf/pmlogmv command injection, libpcp PDU decode
OOB guards, timezone validation, pmdaroot peer credentials, and pmproxy
REST CERT_REQD and logger authentication hardening.

Resolves: RHEL-213756 CVE-2026-16531
Resolves: RHEL-213746 CVE-2026-16530
Resolves: RHEL-213726 CVE-2026-16529
Resolves: RHEL-213717 CVE-2026-16527
Resolves: RHEL-213692 CVE-2026-16526
Resolves: RHEL-213661 CVE-2026-16524
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 12:10:43 +02:00

207 lines
6.2 KiB
Diff

From 4121ae06f5 Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
Subject: [PATCH] pmproxy: add optional authentication for logger servlet
The pmproxy logger servlet endpoints (/logger/label, /logger/meta,
/logger/index, /logger/volume) are registered unconditionally with no
authentication check, allowing any HTTP client to submit archive data.
Add a new pmproxy.conf option [pmlogger] authenticate = true that
enables HTTP Basic authentication for all logger servlet requests.
When set, requests without valid credentials are rejected with
HTTP 403 Forbidden. Disabled by default to preserve existing behavior.
This complements the global -S flag: -S requires authentication for
all servlets, while [pmlogger] authenticate = true targets only the
logger servlet endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
diff --git a/qa/2108 b/qa/2108
new file mode 100755
index 0000000000..5e0d75c494
--- /dev/null
+++ b/qa/2108
@@ -0,0 +1,97 @@
+#!/bin/sh
+# PCP QA Test No. 2108
+# Verify pmproxy logger servlet authentication via pmproxy.conf
+# [pmlogger] authenticate = true
+#
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
+#
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+# get standard environment, filters and checks
+. ./common.product
+. ./common.filter
+. ./common.check
+
+which curl >/dev/null 2>&1 || _notrun "no curl executable installed"
+
+_cleanup()
+{
+ [ -n "$__pid" ] && kill $__pid 2>/dev/null
+ wait $__pid 2>/dev/null
+ cd $here
+ $sudo rm -rf $tmp $tmp.*
+}
+
+status=0 # success is the default!
+__pid=""
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# real QA test starts here
+
+echo "=== logger servlet with authenticate = true ==="
+__port=`_find_free_port`
+cat >$tmp.conf <<EOF
+[pmproxy]
+pcp.enabled = true
+http.enabled = true
+[pmlogger]
+enabled = true
+authenticate = true
+EOF
+$PCP_BINADM_DIR/pmproxy -f -p $__port -l $tmp.log -c $tmp.conf &
+__pid=$!
+sleep 1
+if ! kill -0 $__pid 2>/dev/null; then
+ echo "FAIL: pmproxy did not start"
+ exit
+fi
+
+# unauthenticated POST to logger/label should be rejected
+__code=$(curl -s -o /dev/null -w '%{http_code}' \
+ -X POST "http://localhost:$__port/logger/label" \
+ -H 'Content-Type: application/octet-stream' \
+ --data-binary 'dummy' 2>/dev/null)
+echo "unauthenticated POST /logger/label: HTTP $__code"
+
+# unauthenticated GET to pmapi should still work (not logger servlet)
+__code=$(curl -s -o /dev/null -w '%{http_code}' \
+ "http://localhost:$__port/pmapi/context?hostspec=localhost" 2>/dev/null)
+echo "unauthenticated GET /pmapi/context: HTTP $__code"
+
+kill $__pid
+wait $__pid 2>/dev/null
+__pid=""
+
+echo
+echo "=== logger servlet without authenticate (default) ==="
+__port=`_find_free_port`
+cat >$tmp.conf2 <<EOF
+[pmproxy]
+pcp.enabled = true
+http.enabled = true
+[pmlogger]
+enabled = true
+EOF
+$PCP_BINADM_DIR/pmproxy -f -p $__port -l $tmp.log2 -c $tmp.conf2 &
+__pid=$!
+sleep 1
+if ! kill -0 $__pid 2>/dev/null; then
+ echo "FAIL: pmproxy did not start"
+ exit
+fi
+
+# unauthenticated POST to logger/label should be allowed (will fail on bad data, not auth)
+__code=$(curl -s -o /dev/null -w '%{http_code}' \
+ -X POST "http://localhost:$__port/logger/label" \
+ -H 'Content-Type: application/octet-stream' \
+ --data-binary 'dummy' 2>/dev/null)
+echo "unauthenticated POST /logger/label: HTTP $__code"
+
+kill $__pid
+wait $__pid 2>/dev/null
+__pid=""
+
+# success, all done
+exit
diff --git a/qa/2108.out b/qa/2108.out
new file mode 100644
index 0000000000..8321285476
--- /dev/null
+++ b/qa/2108.out
@@ -0,0 +1,7 @@
+QA output created by 2108
+=== logger servlet with authenticate = true ===
+unauthenticated POST /logger/label: HTTP 403
+unauthenticated GET /pmapi/context: HTTP 200
+
+=== logger servlet without authenticate (default) ===
+unauthenticated POST /logger/label: HTTP 400
diff --git a/qa/group b/qa/group
index 1103b1f526..bd2a2453d6 100644
--- a/qa/group
+++ b/qa/group
@@ -2379,5 +2379,6 @@ suse
2102 pmlogmv local security
2103 pmieconf local security
2107 libpcp local security
+2108 pmproxy local security
4751 libpcp threads valgrind local pcp helgrind
9000 other local
diff --git a/src/pmproxy/pmproxy.conf b/src/pmproxy/pmproxy.conf
index 52572ae647..36baa12f52 100644
--- a/src/pmproxy/pmproxy.conf
+++ b/src/pmproxy/pmproxy.conf
@@ -122,6 +122,9 @@ stream.maxlen = 8640
# allow REST API webhook receiving remote pmlogger(1) archive content
enabled = true
+# require HTTP Basic authentication for logger servlet endpoints
+#authenticate = true
+
# bypass persistent storage of pmlogger archives, use key server only
#cached = true
diff --git a/src/pmproxy/src/logger.c b/src/pmproxy/src/logger.c
index 97c5082273..c5f383d247 100644
--- a/src/pmproxy/src/logger.c
+++ b/src/pmproxy/src/logger.c
@@ -106,6 +106,9 @@ on_pmlogger_done(int status, void *arg)
if (status >= 0) {
code = HTTP_STATUS_OK;
body = pmlogger_success;
+ } else if (client->u.http.parser.status_code) {
+ code = client->u.http.parser.status_code;
+ body = pmlogger_failure;
} else {
if (status == -EEXIST)
code = HTTP_STATUS_CONFLICT;
@@ -139,6 +142,8 @@ on_pmlogger_info(pmLogLevel level, sds message, void *arg)
proxylog(level, message, baton->client->proxy);
}
+static int pmlogger_authenticate;
+
static pmLogGroupSettings pmlogger_settings = {
.callbacks.on_archive = on_pmlogger_archive,
.callbacks.on_done = on_pmlogger_done,
@@ -273,6 +278,10 @@ pmlogger_request_headers(struct client *client, struct dict *headers)
{
if (pmDebugOptions.http)
fprintf(stderr, "logger servlet headers (client=" PRINTF_P_PFX "%p)\n", client);
+ if (pmlogger_authenticate &&
+ (!client->u.http.username || !client->u.http.password)) {
+ client->u.http.parser.status_code = HTTP_STATUS_FORBIDDEN;
+ }
return 0;
}
@@ -378,6 +387,11 @@ pmlogger_servlet_setup(struct proxy *proxy)
{
mmv_registry_t *registry = proxymetrics(proxy, METRICS_LOGGROUP);
mmv_registry_t *logpaths = proxymetrics(proxy, METRICS_LOGPATHS);
+ sds value;
+
+ if ((value = pmIniFileLookup(proxy->config, "pmlogger", "authenticate"))
+ && strcmp(value, "true") == 0)
+ pmlogger_authenticate = 1;
PARAM_CLIENT = sdsnew("client");