pcp/redhat-issues-RHEL-213720-pmproxy-auth-flags.patch
Jan Kurik 1999e78348
[Consolidated] Backport fixes for pcp (c8s) - fixed MR !150
Fixes the consolidated MR !150
Resolves: RHEL-213662
Resolves: RHEL-213686
Resolves: RHEL-213720
Resolves: RHEL-213729
2026-08-06 08:45:08 +02:00

206 lines
6.9 KiB
Diff

From ccbe2d6cb72c2f3f8969564e59f864e588b3d54e Mon Sep 17 00:00:00 2001
From: Nathan Scott <nathans@redhat.com>
Date: Thu, 2 Jul 2026 15:28:34 +1000
Subject: [PATCH] pmproxy: fix missing -Q and -S authentication flags (CWE-306)
The pmproxy -Q (require client certificate) and -S (require
authenticated clients) flags existed as case blocks in the option
parser but were absent from the short_options string and the longopts
table, making them permanently unreachable. An unauthenticated HTTP
client could access all REST API endpoints including /store and /derive.
Fix:
- Add Q and S to short_options so pmgetopt_r() delivers them
- Add --certreqd and --reqauth entries to the longopts table
- Document both flags in the pmproxy(1) man page
- Add qa/2100 verifying the flags are accepted and that -S correctly
rejects unauthenticated REST API requests with HTTP 403
Note: -S enforcement in the REST API path already exists in http.c and
webapi.c. -Q (CERT_REQD) enforcement is only implemented for the
legacy PCP wire protocol path, not the REST API; this is a pre-existing
limitation to be addressed separately.
Reported-by: Francisco Alisson Bezerra, TIM Security Red Team
Reported-by: Lucas Gabriel Alves, TIM Security Red Team
Reported-by: Massimiliano Brolli, TIM Security Red Team
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
man/man1/pmproxy.1 | 6 +++
qa/2100 | 85 +++++++++++++++++++++++++++++++++++++++
qa/2100.out | 12 ++++++
qa/group | 1 +
src/pmproxy/src/pmproxy.c | 4 +-
5 files changed, 107 insertions(+), 1 deletion(-)
create mode 100755 qa/2100
create mode 100644 qa/2100.out
diff --git a/man/man1/pmproxy.1 b/man/man1/pmproxy.1
index 90ec61be8..30758a114 100644
--- a/man/man1/pmproxy.1
+++ b/man/man1/pmproxy.1
@@ -271,6 +271,9 @@ be unreadable by any user other than the user running the
.B pmproxy
process).
.TP
+\fB\-Q\f1, \fB\-\-certreqd\f1
+Require that all client connections provide a trusted client certificate.
+.TP
\f3\-r\f1 \f2port\f1, \f3\-\-redisport\f1=\f2port\f1
Specify an alternate Redis
.I port
@@ -285,6 +288,9 @@ The default value is
.IR $PCP_RUN_DIR/pmproxy.socket .
This option implies \f3pmproxy\f1 is running in \f3timeseries\f1 mode.
.TP
+\fB\-S\f1, \fB\-\-reqauth\f1
+Require that all client connections be authenticated.
+.TP
\fB\-t\f1, \fB\-\-timeseries\f1
Operate in automatic archive timeseries discovery mode.
This mode of operation will enable the
diff --git a/qa/2100 b/qa/2100
new file mode 100755
index 000000000..1de957a2f
--- /dev/null
+++ b/qa/2100
@@ -0,0 +1,85 @@
+#!/bin/sh
+# PCP QA Test No. 2100
+# Verify pmproxy -Q and -S authentication flags are accepted
+# and that -S (reqauth) enforces authentication on REST API
+#
+# 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 "=== checking -Q and -S appear in usage ==="
+pmproxy --help 2>&1 | grep -E '\-[QS]' | sed -e 's/^ *//'
+
+echo
+echo "=== checking -S enforces authentication on REST API ==="
+__port=`_find_free_port`
+$PCP_BINADM_DIR/pmproxy -S -f -p $__port -l $tmp.log &
+__pid=$!
+sleep 1
+if kill -0 $__pid 2>/dev/null; then
+ echo "pmproxy with -S started"
+
+ # unauthenticated request should be rejected
+ __code=`curl -s -o /dev/null -w '%{http_code}' "http://localhost:$__port/pmapi/context?hostspec=localhost" 2>/dev/null`
+ if [ "$__code" = "403" ]; then
+ echo "unauthenticated request correctly rejected (HTTP $__code)"
+ else
+ echo "FAIL: expected HTTP 403, got HTTP $__code"
+ fi
+
+ kill $__pid
+ wait $__pid 2>/dev/null
+ __pid=""
+else
+ echo "FAIL: pmproxy with -S did not start"
+fi
+
+echo
+echo "=== checking without -S allows unauthenticated access ==="
+__port=`_find_free_port`
+$PCP_BINADM_DIR/pmproxy -f -p $__port -l $tmp.log2 &
+__pid=$!
+sleep 1
+if kill -0 $__pid 2>/dev/null; then
+ echo "pmproxy without -S started"
+
+ # unauthenticated request should succeed
+ __code=`curl -s -o /dev/null -w '%{http_code}' "http://localhost:$__port/pmapi/context?hostspec=localhost" 2>/dev/null`
+ if [ "$__code" = "200" ]; then
+ echo "unauthenticated request correctly allowed (HTTP $__code)"
+ else
+ echo "FAIL: expected HTTP 200, got HTTP $__code"
+ fi
+
+ kill $__pid
+ wait $__pid 2>/dev/null
+ __pid=""
+else
+ echo "FAIL: pmproxy without -S did not start"
+fi
+
+# success, all done
+exit
diff --git a/qa/2100.out b/qa/2100.out
new file mode 100644
index 000000000..5302aea77
--- /dev/null
+++ b/qa/2100.out
@@ -0,0 +1,12 @@
+QA output created by 2100
+=== checking -Q and -S appear in usage ===
+-Q, --certreqd require client certificate authentication
+-S, --reqauth require all client connections to be authenticated
+
+=== checking -S enforces authentication on REST API ===
+pmproxy with -S started
+unauthenticated request correctly rejected (HTTP 403)
+
+=== checking without -S allows unauthenticated access ===
+pmproxy without -S started
+unauthenticated request correctly allowed (HTTP 200)
diff --git a/qa/group b/qa/group
index 742d3b54b..a8b52b68e 100644
--- a/qa/group
+++ b/qa/group
@@ -1980,5 +1980,6 @@ x11
1984 pmlogconf pmda.redis local
1985 pmfind local valgrind
1986 pmfind local
+2100 pmproxy local security
2105 libpcp pmcd local security pmcd.pdu
4751 libpcp threads valgrind local pcp helgrind
diff --git a/src/pmproxy/src/pmproxy.c b/src/pmproxy/src/pmproxy.c
index d975ef37a..43766c20f 100644
--- a/src/pmproxy/src/pmproxy.c
+++ b/src/pmproxy/src/pmproxy.c
@@ -86,7 +86,9 @@ static pmLongOptions longopts[] = {
PMAPI_OPTIONS_HEADER("Connection options"),
{ "interface", 1, 'i', "ADDR", "accept connections on this IP address" },
{ "port", 1, 'p', "PORT", "accept connections on this port" },
+ { "certreqd", 0, 'Q', 0, "require client certificate authentication" },
{ "socket", 1, 's', "PATH", "Unix domain socket file [default $PCP_RUN_DIR/pmproxy.socket]" },
+ { "reqauth", 0, 'S', 0, "require all client connections to be authenticated" },
{ "redisport", 1, 'r', "PORT", "Connect to Redis instance on this TCP/IP port (implies --timeseries)" },
{ "redishost", 1, 'h', "HOST", "Connect to Redis instance on this host name (implies --timeseries)" },
PMAPI_OPTIONS_HEADER("Diagnostic options"),
@@ -96,7 +98,7 @@ static pmLongOptions longopts[] = {
};
static pmOptions opts = {
- .short_options = "Ac:C:dD:Ffh:i:l:L:M:p:P:r:s:tU:x:?",
+ .short_options = "Ac:C:dD:Ffh:i:l:L:M:p:P:Qr:s:StU:x:?",
.long_options = longopts,
};