Compare commits

..

No commits in common. "c8" and "c9-beta" have entirely different histories.
c8 ... c9-beta

10 changed files with 1352 additions and 743 deletions

40
.gitignore vendored
View File

@ -1,22 +1,22 @@
SOURCES/HAM-logo.png SOURCES/backports-3.25.3.gem
SOURCES/backports-3.24.1.gem SOURCES/base64-0.2.0.gem
SOURCES/dacite-1.8.1.tar.gz SOURCES/childprocess-5.1.0.gem
SOURCES/dataclasses-0.8.tar.gz SOURCES/dacite-1.9.2.tar.gz
SOURCES/ethon-0.16.0.gem SOURCES/ethon-0.18.0.gem
SOURCES/ffi-1.16.3.gem SOURCES/ffi-1.17.2.gem
SOURCES/json-2.6.3.gem SOURCES/logger-1.7.0.gem
SOURCES/mustermann-2.0.2.gem SOURCES/mustermann-3.1.1.gem
SOURCES/nio4r-2.5.9.gem SOURCES/nio4r-2.7.5.gem
SOURCES/open4-1.3.4-1.gem SOURCES/pcs-0.11.12.tar.gz
SOURCES/pcs-0.10.18.tar.gz SOURCES/pcs-web-ui-0.1.25.tar.gz
SOURCES/puma-6.4.0.gem SOURCES/pcs-web-ui-node-modules-0.1.25.tar.xz
SOURCES/puma-8.0.1.gem
SOURCES/pyagentx-0.4.pcs.2.tar.gz SOURCES/pyagentx-0.4.pcs.2.tar.gz
SOURCES/python-dateutil-2.8.2.tar.gz SOURCES/rack-3.2.6.gem
SOURCES/rack-2.2.20.gem SOURCES/rack-protection-4.2.1.gem
SOURCES/rack-protection-2.2.4.gem SOURCES/rack-session-2.1.2.gem
SOURCES/rack-test-2.1.0.gem SOURCES/rack-test-2.2.0.gem
SOURCES/rexml-3.4.1.gem SOURCES/rackup-2.3.1.gem
SOURCES/ruby2_keywords-0.0.5.gem SOURCES/ruby2_keywords-0.0.5.gem
SOURCES/sinatra-2.2.4.gem SOURCES/sinatra-4.2.1.gem
SOURCES/tilt-2.3.0.gem SOURCES/tilt-2.7.0.gem
SOURCES/tornado-v6.1.0.pcs.4.tar.gz

View File

@ -1,22 +1,22 @@
679a4ce22a33ffd4d704261a17c00cff98d9499a SOURCES/HAM-logo.png 41b95975c12381bcd9cbc32bd278440313fce12d SOURCES/backports-3.25.3.gem
0ef72a288913e220695ad62718aeb75171924028 SOURCES/backports-3.24.1.gem ea3a591bdfa93655d8eec9d7bdd7fb87ecb5616a SOURCES/base64-0.2.0.gem
07b26abbf7ff0dcba5c7f9e814ff7eebafefb058 SOURCES/dacite-1.8.1.tar.gz 963b12b359251f41998eebe6f6adfec92fe6f49f SOURCES/childprocess-5.1.0.gem
8b7598273d2ae6dad2b88466aefac55071a41926 SOURCES/dataclasses-0.8.tar.gz 01690d9883c149890e04dce4db43ec305959aa39 SOURCES/dacite-1.9.2.tar.gz
5b56a68268708c474bef04550639ded3add5e946 SOURCES/ethon-0.16.0.gem ffd8b3e5ac044a1a69791411e3e5cf4b5d4a6768 SOURCES/ethon-0.18.0.gem
10e4cf0e11ef4581ec4ad5fe2cdf3c78b6077d39 SOURCES/ffi-1.16.3.gem 01747fce469e932b701cb7a35d1ef4b3c68eb170 SOURCES/ffi-1.17.2.gem
6d78f730b7f3b25fb3f93684fe1364acf58bce6b SOURCES/json-2.6.3.gem abfa641d98ab2e71bc8102b0aab2f466569668d2 SOURCES/logger-1.7.0.gem
f5f804366823c1126791dfefd98dd0539563785c SOURCES/mustermann-2.0.2.gem 0b23c093481361b6b7dc4c733407e28f6aba181a SOURCES/mustermann-3.1.1.gem
2f65d371f5f37460ad74afcedcb97d2b41a46806 SOURCES/nio4r-2.5.9.gem 34420ab7703c0033e18680504a9f8899322ac908 SOURCES/nio4r-2.7.5.gem
41a7fe9f8e3e02da5ae76c821b89c5b376a97746 SOURCES/open4-1.3.4-1.gem 4377db28b20fbf93819f3d719545d644e93b9886 SOURCES/pcs-0.11.12.tar.gz
b3cd873042b17021355b68f1f7aa313f0c1f3fee SOURCES/pcs-0.10.18.tar.gz 76c4bde0b534e8ce82ad2b2f3b155faaa09ae6fb SOURCES/pcs-web-ui-0.1.25.tar.gz
d6049c4555f3c9d198e6eb1d7e53ce9b68e175ff SOURCES/puma-6.4.0.gem 384635d68c0648c3d18c53c0c7a93227167841bf SOURCES/pcs-web-ui-node-modules-0.1.25.tar.xz
7fb9184ad87327fd94ea3c1f75158d3ca8332d4a SOURCES/puma-8.0.1.gem
3176b2f2b332c2b6bf79fe882e83feecf3d3f011 SOURCES/pyagentx-0.4.pcs.2.tar.gz 3176b2f2b332c2b6bf79fe882e83feecf3d3f011 SOURCES/pyagentx-0.4.pcs.2.tar.gz
c2ba10c775b7a52a4b57cac4d4110a0c0f812a82 SOURCES/python-dateutil-2.8.2.tar.gz 41b3f39ab51fe8584be6f9e849152bb69b06f575 SOURCES/rack-3.2.6.gem
4c52ad6f798e78d4a1800257ef0d7fc5ac254712 SOURCES/rack-2.2.20.gem 1457dded6ffa0f564b33329861dd6b257f07498d SOURCES/rack-protection-4.2.1.gem
5347315a7283f0b04443e924ed4eaa17807432c8 SOURCES/rack-protection-2.2.4.gem 3353c03a5d41f14bfa3459436400fc363ab4fd96 SOURCES/rack-session-2.1.2.gem
ae09ea83748b55875edc3708fffba90db180cb8e SOURCES/rack-test-2.1.0.gem 922c597f0503f97dc3a058fe997590b108bc429a SOURCES/rack-test-2.2.0.gem
966b1564a77719483eb61068ed1dfb638e5e8eb0 SOURCES/rexml-3.4.1.gem f470c1dc5a15e44a241cae4cb50d94b7b3f6b010 SOURCES/rackup-2.3.1.gem
d017b9e4d1978e0b3ccc3e2a31493809e4693cd3 SOURCES/ruby2_keywords-0.0.5.gem d017b9e4d1978e0b3ccc3e2a31493809e4693cd3 SOURCES/ruby2_keywords-0.0.5.gem
fa6a6c98f885e93f54c23dd0454cae906e82c31b SOURCES/sinatra-2.2.4.gem 611999f43e27779278c80acfb5825c7255497988 SOURCES/sinatra-4.2.1.gem
4a38a9a55887b2882182a2c5771e592efe514e5e SOURCES/tilt-2.3.0.gem 93253f48f74242535d1c572e88a5651f2c1565c4 SOURCES/tilt-2.7.0.gem
bf4020626453c8db65d1817bed7b1884ea530e92 SOURCES/tornado-v6.1.0.pcs.4.tar.gz

View File

@ -1,54 +0,0 @@
From 7fc3db518798cbbd4d11028d52837d460640fb06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20Posp=C3=AD=C5=A1il?= <mpospisi@redhat.com>
Date: Wed, 25 Mar 2026 22:01:21 +0100
Subject: [PATCH] disable multipart requests in Tornado
Pcsd doesn't use multipart requests and recently, there have been
vulnerabilities targeting both Rack and Tornado. Since we use Tornado as
a proxy for our Ruby daemon, this also helps mitigate future Rack multipart
vulnerabilities.
Multipart vulnerabilities:
https://www.cve.org/CVERecord?id=CVE-2026-31958
https://www.cve.org/CVERecord?id=CVE-2025-61771
https://www.cve.org/CVERecord?id=CVE-2025-61770
---
pcs/daemon/run.py | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/pcs/daemon/run.py b/pcs/daemon/run.py
index 0a6b1b211..19586b18a 100644
--- a/pcs/daemon/run.py
+++ b/pcs/daemon/run.py
@@ -3,6 +3,11 @@ import signal
import socket
from pathlib import Path
+from tornado.httputil import (
+ ParseBodyConfig,
+ ParseMultipartConfig,
+ set_parse_body_config,
+)
from tornado.ioloop import IOLoop
from tornado.locks import Lock
from tornado.web import Application
@@ -71,6 +76,16 @@ def configure_app(
reload its SSL certificates). A relevant handler should get this
object via the method `initialize`.
"""
+
+ # Disable multipart requests to enhance security due to recent CVEs
+ # https://www.cve.org/CVERecord?id=CVE-2026-31958
+ # https://www.cve.org/CVERecord?id=CVE-2025-61771
+ # https://www.cve.org/CVERecord?id=CVE-2025-61770
+ # https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.set_parse_body_config
+ set_parse_body_config(
+ ParseBodyConfig(multipart=ParseMultipartConfig(enabled=False))
+ )
+
routes = sinatra_remote.get_routes(
ruby_pcsd_wrapper,
sync_config_lock,
--
2.53.0

View File

@ -1,55 +0,0 @@
From 957856a556f5ed92129ce602538c3df3aebce7a3 Mon Sep 17 00:00:00 2001
From: Ivan Devat <idevat@redhat.com>
Date: Tue, 5 Dec 2023 15:18:35 +0100
Subject: [PATCH 2/2] disable alternative webui routes
This commit is intended to be downstream only.
The new web ui was part of rhel8 as a technical preview. But new web ui
is now the main in rhel9 and there is no need to keep it in rhel8.
To prevent unnecessary maintenance burden it is disabled now.
No handler code is removed, just routing disabled.
---
pcs/daemon/run.py | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/pcs/daemon/run.py b/pcs/daemon/run.py
index 7fdeda2a..0a6b1b21 100644
--- a/pcs/daemon/run.py
+++ b/pcs/daemon/run.py
@@ -81,16 +81,22 @@ def configure_app(
routes.extend(
# old web ui by default
[(r"/", RedirectHandler, dict(url="/manage"))]
- + [(r"/ui", RedirectHandler, dict(url="/ui/"))]
- + ui.get_routes(
- url_prefix="/ui/",
- app_dir=os.path.join(public_dir, "ui"),
- fallback_page_path=os.path.join(
- public_dir,
- "ui_instructions.html",
- ),
- session_storage=session_storage,
- )
+ # The following disabled routes was for the new web ui. The new
+ # web ui was here as a technical preview. But new web ui is now
+ # the main in rhel9 and there is no need to keep it in rhel8.
+ # To prevent unnecessary maintenance burden it is disabled now.
+ # No handler code is removed, just routing disabled.
+ #
+ # + [(r"/ui", RedirectHandler, dict(url="/ui/"))]
+ # + ui.get_routes(
+ # url_prefix="/ui/",
+ # app_dir=os.path.join(public_dir, "ui"),
+ # fallback_page_path=os.path.join(
+ # public_dir,
+ # "ui_instructions.html",
+ # ),
+ # session_storage=session_storage,
+ # )
+ sinatra_ui.get_routes(
session_storage, ruby_pcsd_wrapper, public_dir
)
--
2.43.0

View File

@ -0,0 +1,235 @@
From cab85b898e69e9eb7590f10449b5c134b3b6e0ed Mon Sep 17 00:00:00 2001
From: Ivan Devat <idevat@redhat.com>
Date: Wed, 3 Jun 2026 13:25:11 +0200
Subject: [PATCH] Revert "feat: deprecate multi cluster management"
This reverts commit f26ee4ce4c2fc1e2ed15d0bf62e4ebeb57a72d50.
---
CHANGELOG.md | 5 --
packages/app/Makefile.am | 1 -
.../src/app/view/dashboard/DashboardApp.tsx | 36 --------------
.../useDashboardDeprecationDismiss.ts | 21 ---------
.../src/app/view/dataTest/json/dashboard.json | 4 --
packages/test/Makefile.am | 1 -
.../dashboard/deprecation-alert.test.ts | 47 -------------------
7 files changed, 115 deletions(-)
delete mode 100644 packages/app/src/app/view/dashboard/useDashboardDeprecationDismiss.ts
delete mode 100644 packages/test/src/test/scenes/dashboard/deprecation-alert.test.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bea609c9..5f66dce2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,11 +6,6 @@
* Support for pacemaker CIB secrets ([RHEL-7604])
-### Deprecated
-
-* Multi-cluster management is deprecated and will be removed in a future version
- ([RHEL-148956])
-
### Fixed
* Omit empty link options in cluster setup request
diff --git a/packages/app/Makefile.am b/packages/app/Makefile.am
index f2919961..15b98600 100644
--- a/packages/app/Makefile.am
+++ b/packages/app/Makefile.am
@@ -560,7 +560,6 @@ EXTRA_DIST = \
src/app/view/dashboard/DashboardApp.tsx \
src/app/view/dashboard/DashboardToolbar.tsx \
src/app/view/dashboard/index.ts \
- src/app/view/dashboard/useDashboardDeprecationDismiss.ts \
src/app/view/dataTest/index.ts \
src/app/view/dataTest/json/cluster/acl.json \
src/app/view/dataTest/json/cluster/aclToolbar.json \
diff --git a/packages/app/src/app/view/dashboard/DashboardApp.tsx b/packages/app/src/app/view/dashboard/DashboardApp.tsx
index 80404724..b5d23d2a 100644
--- a/packages/app/src/app/view/dashboard/DashboardApp.tsx
+++ b/packages/app/src/app/view/dashboard/DashboardApp.tsx
@@ -1,16 +1,12 @@
import React from "react";
import {useSelector} from "react-redux";
import {
- Alert,
- AlertActionCloseButton,
Breadcrumb,
BreadcrumbItem,
- Button,
PageSection,
Stack,
StackItem,
} from "@patternfly/react-core";
-import {InfoCircleIcon} from "@patternfly/react-icons";
import {testMarks} from "app/view/dataTest";
import {selectors} from "app/store";
@@ -25,7 +21,6 @@ import {
import {DashboardClusterList} from "./clusterList";
import {DashboardToolbar} from "./DashboardToolbar";
-import {useDashboardDeprecationDismiss} from "./useDashboardDeprecationDismiss";
const useDashboardSync = () => {
const dispatch = useDispatch();
@@ -51,7 +46,6 @@ export const DashboardApp = () => {
const dispatch = useDispatch();
const importedClusterNameList = useSelector(selectors.getImportedClusterList);
const loading = useSelector(selectors.dashboardGetLoadingStatus);
- const {dismissed, dismiss, restore} = useDashboardDeprecationDismiss();
return (
<Page>
@@ -69,17 +63,6 @@ export const DashboardApp = () => {
when={loading.when}
isLoading={loading.currently}
/>
- {dismissed && (
- <Button
- variant="link"
- icon={<InfoCircleIcon />}
- onClick={restore}
- {...testMarks.dashboard.deprecationAlert.showNotices
- .mark}
- >
- Deprecation
- </Button>
- )}
</BreadcrumbItem>
</Breadcrumb>
}
@@ -90,25 +73,6 @@ export const DashboardApp = () => {
</StackItem>
</Stack>
</PageSection>
- {!dismissed && (
- <PageSection>
- <Alert
- variant="info"
- isInline
- title="Multi-cluster management is deprecated"
- actionClose={
- <AlertActionCloseButton
- {...testMarks.dashboard.deprecationAlert.close.mark}
- onClose={dismiss}
- />
- }
- {...testMarks.dashboard.deprecationAlert.mark}
- >
- Management of multiple clusters will be removed in a future
- version.
- </Alert>
- </PageSection>
- )}
<PageSection {...testMarks.dashboard.mark}>
{loading.status === "SUCCESS" && (
<DashboardClusterList
diff --git a/packages/app/src/app/view/dashboard/useDashboardDeprecationDismiss.ts b/packages/app/src/app/view/dashboard/useDashboardDeprecationDismiss.ts
deleted file mode 100644
index 88fead22..00000000
--- a/packages/app/src/app/view/dashboard/useDashboardDeprecationDismiss.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from "react";
-
-const storageKey = "deprecationMultiClusterDismissed";
-
-export const useDashboardDeprecationDismiss = () => {
- const [dismissed, setDismissed] = React.useState(
- () => localStorage.getItem(storageKey) === "true",
- );
-
- const dismiss = () => {
- localStorage.setItem(storageKey, "true");
- setDismissed(true);
- };
-
- const restore = () => {
- localStorage.removeItem(storageKey);
- setDismissed(false);
- };
-
- return {dismissed, dismiss, restore};
-};
diff --git a/packages/app/src/app/view/dataTest/json/dashboard.json b/packages/app/src/app/view/dataTest/json/dashboard.json
index 7d92a3ab..909f3fd4 100644
--- a/packages/app/src/app/view/dataTest/json/dashboard.json
+++ b/packages/app/src/app/view/dataTest/json/dashboard.json
@@ -1,8 +1,4 @@
{
- "deprecationAlert": {
- "close": {},
- "showNotices": {}
- },
"clusterList": {
"cluster": {
"actions": {
diff --git a/packages/test/Makefile.am b/packages/test/Makefile.am
index b0cd2c38..d7c201c0 100644
--- a/packages/test/Makefile.am
+++ b/packages/test/Makefile.am
@@ -43,7 +43,6 @@ EXTRA_DIST = \
src/test/scenes/dashboard/cluster-status.test.ts \
src/test/scenes/dashboard/cluster-stop.test.ts \
src/test/scenes/dashboard/common.ts \
- src/test/scenes/dashboard/deprecation-alert.test.ts \
src/test/scenes/dashboard/import-existring-cluster.test.ts \
src/test/scenes/dashboard/to-cluster-transition.test.ts \
src/test/scenes/fence-devices/arguments-view.test.ts \
diff --git a/packages/test/src/test/scenes/dashboard/deprecation-alert.test.ts b/packages/test/src/test/scenes/dashboard/deprecation-alert.test.ts
deleted file mode 100644
index bba245e4..00000000
--- a/packages/test/src/test/scenes/dashboard/deprecation-alert.test.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import {mock} from "test/tools";
-
-const {deprecationAlert} = marks.dashboard;
-
-describe("Deprecation alert", () => {
- beforeEach(async () => {
- await page.evaluate(() =>
- localStorage.removeItem("deprecationMultiClusterDismissed"),
- );
- mock.shortcuts.withDashboard({clusterStatus: []});
- });
-
- afterEach(mock.stop);
-
- it("should display deprecation alert", async () => {
- await goToDashboard();
- await isVisible(deprecationAlert);
- });
-
- it("should hide alert after clicking close", async () => {
- await goToDashboard();
- await click(deprecationAlert.close);
- await isAbsent(deprecationAlert);
- });
-
- it("should show 'Show deprecation notices' after dismiss", async () => {
- await goToDashboard();
- await click(deprecationAlert.close);
- await isVisible(deprecationAlert.showNotices);
- });
-
- it("should restore alert when clicking 'Show deprecation notices'", async () => {
- await goToDashboard();
- await click(deprecationAlert.close);
- await click(deprecationAlert.showNotices);
- await isVisible(deprecationAlert);
- });
-
- it("should stay hidden when previously dismissed", async () => {
- await page.evaluate(() =>
- localStorage.setItem("deprecationMultiClusterDismissed", "true"),
- );
- await goToDashboard();
- await isAbsent(deprecationAlert);
- await isVisible(deprecationAlert.showNotices);
- });
-});
--
2.54.0

View File

@ -0,0 +1,480 @@
From 68354f8f91ea74ecf90333f33e7d4789ec93efa6 Mon Sep 17 00:00:00 2001
From: Miroslav Lisik <mlisik@redhat.com>
Date: Thu, 11 Jun 2026 16:23:08 +0200
Subject: [PATCH] fix `pcs stonith update-scsi-devices` command
* use new cibadmin option `--update-status` when updating scsi devices
---
pcs/lib/commands/stonith.py | 9 ++-
pcs/lib/env.py | 55 +++++++++----------
pcs/lib/pacemaker/live.py | 12 +++-
.../test_stonith_update_scsi_devices.py | 27 +++++++++
pcs_test/tier0/lib/test_env_cib.py | 21 +++++++
pcs_test/tools/command_env/config_env.py | 8 ++-
.../tools/command_env/config_runner_cib.py | 7 ++-
.../tools/command_env/config_runner_pcmk.py | 15 +++++
pcs_test/tools/command_env/mock_push_cib.py | 30 +++++++++-
9 files changed, 146 insertions(+), 38 deletions(-)
diff --git a/pcs/lib/commands/stonith.py b/pcs/lib/commands/stonith.py
index c5c1ab4b3..15a1efd52 100644
--- a/pcs/lib/commands/stonith.py
+++ b/pcs/lib/commands/stonith.py
@@ -42,6 +42,7 @@ from pcs.lib.pacemaker.live import (
fence_history_cleanup,
fence_history_text,
fence_history_update,
+ is_cibadmin_update_status_supported,
is_fence_history_supported_management,
is_getting_resource_digest_supported,
)
@@ -402,7 +403,9 @@ def _update_scsi_devices_get_element_and_devices(
cib -- cib element
stonith_id -- id of stonith resource
"""
- if not is_getting_resource_digest_supported(runner):
+ if not is_getting_resource_digest_supported(
+ runner
+ ) or not is_cibadmin_update_status_supported(runner):
raise LibraryError(
ReportItem.error(
reports.messages.StonithRestartlessUpdateOfScsiDevicesNotSupported()
@@ -529,7 +532,7 @@ def update_scsi_devices(
_unfencing_scsi_devices(
env, stonith_el, current_device_list, set_device_list, force_flags
)
- env.push_cib()
+ env.push_cib(with_status=True)
def update_scsi_devices_add_remove(
@@ -583,4 +586,4 @@ def update_scsi_devices_add_remove(
_unfencing_scsi_devices(
env, stonith_el, current_device_list, updated_device_set, force_flags
)
- env.push_cib()
+ env.push_cib(with_status=True)
diff --git a/pcs/lib/env.py b/pcs/lib/env.py
index d0cffc993..eea9e90bc 100644
--- a/pcs/lib/env.py
+++ b/pcs/lib/env.py
@@ -1,24 +1,11 @@
from logging import Logger
-from typing import (
- Any,
- Callable,
- Mapping,
- Optional,
- Union,
- cast,
-)
+from typing import Any, Callable, Mapping, Optional, Union, cast
from lxml.etree import _Element
-from pcs.common import (
- file_type_codes,
- reports,
-)
+from pcs.common import file_type_codes, reports
from pcs.common.host import PcsKnownHost
-from pcs.common.node_communicator import (
- Communicator,
- NodeCommunicatorFactory,
-)
+from pcs.common.node_communicator import Communicator, NodeCommunicatorFactory
from pcs.common.reports import ReportProcessor
from pcs.common.reports.item import ReportItem
from pcs.common.services.interfaces import ServiceManagerInterface
@@ -31,10 +18,7 @@ from pcs.lib.communication.corosync import (
DistributeCorosyncConf,
ReloadCorosyncConf,
)
-from pcs.lib.communication.tools import (
- run,
- run_and_raise,
-)
+from pcs.lib.communication.tools import run, run_and_raise
from pcs.lib.corosync.config_facade import ConfigFacade as CorosyncConfigFacade
from pcs.lib.corosync.config_parser import (
verify_section as verify_corosync_section,
@@ -245,15 +229,21 @@ class LibraryEnvironment:
ReportItem.error(reports.messages.WaitForIdleNotLiveCluster())
)
- def push_cib(self, custom_cib=None, wait_timeout: int = -1) -> None:
+ def push_cib(
+ self,
+ custom_cib: Optional[_Element] = None,
+ wait_timeout: int = -1,
+ with_status: bool = False,
+ ) -> None:
"""
Push previously loaded instance of CIB or a custom CIB
- etree custom_cib -- push a custom CIB instead of a loaded instance
+ custom_cib -- push a custom CIB instead of a loaded instance
(allows to push an externally provided CIB and replace the one in
the cluster completely)
wait_timeout -- wait timeout in seconds, if less than 0 wait will be
skipped, if 0 wait indefinitely
+ with_status -- push also status section of a CIB
"""
self._ensure_wait_satisfiable(wait_timeout)
if custom_cib is not None:
@@ -261,10 +251,14 @@ class LibraryEnvironment:
raise AssertionError(
"CIB has been loaded, cannot push custom CIB"
)
+ if with_status:
+ raise AssertionError(
+ "Cannot push status section of a custom CIB"
+ )
return self.__push_cib_full(custom_cib, wait_timeout)
if self.__loaded_cib_diff_source is None:
raise AssertionError("CIB has not been loaded")
- return self.__push_cib_diff(wait_timeout)
+ return self.__push_cib_diff(wait_timeout, with_status)
def __push_cib_full(self, cib_to_push, wait_timeout: int):
self.__do_push_cib(
@@ -272,20 +266,23 @@ class LibraryEnvironment:
wait_timeout,
)
- def __push_cib_diff(self, wait_timeout: int):
+ def __push_cib_diff(self, wait_timeout: int, with_status: bool = False):
self.__do_push_cib(
- lambda: self.__main_push_cib_diff(self.cmd_runner()), wait_timeout
+ lambda: self.__main_push_cib_diff(self.cmd_runner(), with_status),
+ wait_timeout,
)
- def __main_push_cib_diff(self, cmd_runner):
+ def __main_push_cib_diff(
+ self, cmd_runner: CommandRunner, with_status: bool = False
+ ):
cib_diff_xml = diff_cibs_xml(
cmd_runner,
self.report_processor,
- self.__loaded_cib_diff_source,
- etree_to_str(self.__loaded_cib_to_modify),
+ cast(str, self.__loaded_cib_diff_source),
+ etree_to_str(cast(_Element, self.__loaded_cib_to_modify)),
)
if cib_diff_xml:
- push_cib_diff_xml(cmd_runner, cib_diff_xml)
+ push_cib_diff_xml(cmd_runner, cib_diff_xml, with_status)
def __do_push_cib(self, push_strategy, wait_timeout: int) -> None:
push_strategy()
diff --git a/pcs/lib/pacemaker/live.py b/pcs/lib/pacemaker/live.py
index da1154f53..97cd7da59 100644
--- a/pcs/lib/pacemaker/live.py
+++ b/pcs/lib/pacemaker/live.py
@@ -290,13 +290,17 @@ def replace_cib_configuration(runner: CommandRunner, tree: _Element) -> None:
return replace_cib_configuration_xml(runner, etree_to_str(tree))
-def push_cib_diff_xml(runner: CommandRunner, cib_diff_xml: str) -> None:
+def push_cib_diff_xml(
+ runner: CommandRunner, cib_diff_xml: str, with_status: bool = False
+) -> None:
cmd = [
settings.cibadmin_exec,
"--patch",
"--verbose",
"--xml-pipe",
]
+ if with_status:
+ cmd.append("--update-status")
stdout, stderr, retval = runner.run(cmd, stdin_string=cib_diff_xml)
if retval != 0:
raise LibraryError(
@@ -965,6 +969,12 @@ def is_getting_resource_digest_supported(runner: CommandRunner) -> bool:
)
+def is_cibadmin_update_status_supported(runner: CommandRunner) -> bool:
+ return _is_in_pcmk_tool_help(
+ runner, settings.cibadmin_exec, ["--update-status"]
+ )
+
+
def get_resource_digests(
runner: CommandRunner,
resource_id: str,
diff --git a/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py b/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py
index 152b51cae..3148d6815 100644
--- a/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py
+++ b/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py
@@ -417,6 +417,7 @@ class UpdateScsiDevicesMixin:
),
)
self.config.runner.pcmk.is_resource_digests_supported()
+ self.config.runner.pcmk.is_cibadmin_update_status_supported()
self.config.runner.pcmk.load_state(
resources=fixture_crm_mon_res_running(
self.stonith_id,
@@ -518,6 +519,7 @@ class UpdateScsiDevicesMixin:
lrm_monitor_ops=lrm_monitor_ops_updated,
digests_attrs_list=digests_attrs_list_updated,
),
+ with_status=True,
)
kwargs = dict(devices_updated=devices_updated)
if devices_add is not None:
@@ -560,6 +562,26 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
expected_in_processor=False,
)
+ def test_cibadmin_doesnt_support_update_status(self):
+ self.config.runner.cib.load(
+ resources=fixture_scsi(
+ stonith_id=self.stonith_id, stonith_type=self.stonith_type
+ )
+ )
+ self.config.runner.pcmk.is_resource_digests_supported()
+ self.config.runner.pcmk.is_cibadmin_update_status_supported(
+ is_supported=False
+ )
+ self.env_assist.assert_raise_library_error(
+ self.command(),
+ [
+ fixture.error(
+ reports.codes.STONITH_RESTARTLESS_UPDATE_OF_SCSI_DEVICES_NOT_SUPPORTED,
+ )
+ ],
+ expected_in_processor=False,
+ )
+
def test_nonexistent_id(self):
"""
lower level tested in
@@ -572,6 +594,7 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
)
)
self.config.runner.pcmk.is_resource_digests_supported()
+ self.config.runner.pcmk.is_cibadmin_update_status_supported()
self.env_assist.assert_raise_library_error(self.command())
self.env_assist.assert_reports(
[
@@ -597,6 +620,7 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
)
)
self.config.runner.pcmk.is_resource_digests_supported()
+ self.config.runner.pcmk.is_cibadmin_update_status_supported()
self.env_assist.assert_raise_library_error(
self.command(devices_add=[DEV_2], devices_remove=[DEV_1])
)
@@ -949,6 +973,7 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
lrm_start_ops=DEFAULT_LRM_START_OPS_UPDATED,
lrm_monitor_ops=DEFAULT_LRM_MONITOR_OPS_UPDATED,
),
+ with_status=True,
)
self.command(force_flags=[reports.codes.SKIP_OFFLINE_NODES])()
self.env_assist.assert_reports(
@@ -1240,6 +1265,7 @@ class UpdateScsiDevicesSetFailuresBaseMixin(
)
)
self.config.runner.pcmk.is_resource_digests_supported()
+ self.config.runner.pcmk.is_cibadmin_update_status_supported()
self.env_assist.assert_raise_library_error(
self.command(devices_updated=())
)
@@ -1271,6 +1297,7 @@ class UpdateScsiDevicesAddRemoveFailuresBaseMixin(
)
)
self.config.runner.pcmk.is_resource_digests_supported()
+ self.config.runner.pcmk.is_cibadmin_update_status_supported()
self.env_assist.assert_raise_library_error(
self.command(devices_add=(), devices_remove=())
)
diff --git a/pcs_test/tier0/lib/test_env_cib.py b/pcs_test/tier0/lib/test_env_cib.py
index e23a55a98..8f762e809 100644
--- a/pcs_test/tier0/lib/test_env_cib.py
+++ b/pcs_test/tier0/lib/test_env_cib.py
@@ -52,6 +52,11 @@ class ManageCibAssertionMixin:
callable_obj, "CIB has been loaded, cannot push custom CIB"
)
+ def assert_raises_cannot_push_status_custom(self, callable_obj):
+ self.assert_raises_cib_error(
+ callable_obj, "Cannot push status section of a custom CIB"
+ )
+
class IsCibLive(TestCase):
def test_is_live_when_no_cib_data_specified(self):
@@ -385,6 +390,16 @@ class PushLoadedCib(TestCase, ManageCibAssertionMixin):
]
)
+ def test_with_status(self):
+ self.config_load_cib_files()
+ self.config.runner.cib.diff(self.tmpfile_old, self.tmpfile_new)
+ self.config.runner.cib.push_diff(with_status=True)
+ env = self.env_assist.get_env()
+
+ env.get_cib()
+ env.push_cib(with_status=True)
+ self.env_assist.assert_reports(self.push_reports())
+
class PushCustomCib(TestCase, ManageCibAssertionMixin):
custom_cib = "<custom_cib />"
@@ -424,6 +439,12 @@ class PushCustomCib(TestCase, ManageCibAssertionMixin):
]
)
+ def test_with_status(self):
+ env = self.env_assist.get_env()
+ self.assert_raises_cannot_push_status_custom(
+ partial(env.push_cib, etree.XML(self.custom_cib), with_status=True)
+ )
+
class PushCibMockedWithWait(TestCase):
def setUp(self):
diff --git a/pcs_test/tools/command_env/config_env.py b/pcs_test/tools/command_env/config_env.py
index b421c0b21..4782d62db 100644
--- a/pcs_test/tools/command_env/config_env.py
+++ b/pcs_test/tools/command_env/config_env.py
@@ -89,6 +89,7 @@ class EnvConfig:
wait=-1,
exception=None,
instead=None,
+ with_status=False,
**modifier_shortcuts,
):
"""
@@ -115,7 +116,12 @@ class EnvConfig:
)
self.__calls.place(
name,
- PushCibCall(cib_xml, wait_timeout=wait, exception=exception),
+ PushCibCall(
+ cib_xml,
+ wait_timeout=wait,
+ exception=exception,
+ with_status=with_status,
+ ),
instead=instead,
)
diff --git a/pcs_test/tools/command_env/config_runner_cib.py b/pcs_test/tools/command_env/config_runner_cib.py
index 16273b2b0..fabac5a28 100644
--- a/pcs_test/tools/command_env/config_runner_cib.py
+++ b/pcs_test/tools/command_env/config_runner_cib.py
@@ -227,17 +227,22 @@ class CibShortcuts:
stderr="",
returncode=0,
env=None,
+ with_status=False,
):
"""
Create a call for pushing a diff of CIBs
string name -- key of the call
string cib_diff -- the diff of CIBs
dict env -- CommandRunner environment variables
+ bool with_status -- if True, expect --update-status flag
"""
+ cmd = ["cibadmin", "--patch", "--verbose", "--xml-pipe"]
+ if with_status:
+ cmd.append("--update-status")
self.__calls.place(
name,
RunnerCall(
- ["cibadmin", "--patch", "--verbose", "--xml-pipe"],
+ cmd,
check_stdin=CheckStdinEqualXml(cib_diff),
stdout=stdout,
stderr=stderr,
diff --git a/pcs_test/tools/command_env/config_runner_pcmk.py b/pcs_test/tools/command_env/config_runner_pcmk.py
index 778c5b67e..c944d2f7f 100644
--- a/pcs_test/tools/command_env/config_runner_pcmk.py
+++ b/pcs_test/tools/command_env/config_runner_pcmk.py
@@ -885,6 +885,21 @@ class PcmkShortcuts:
),
)
+ def is_cibadmin_update_status_supported(
+ self,
+ name="runner.pcmk.is_cibadmin_update_status_supported",
+ is_supported=True,
+ ):
+ self.__calls.place(
+ name,
+ RunnerCall(
+ ["cibadmin", "--help-all"],
+ stdout="--update-status" if is_supported else "",
+ stderr="",
+ returncode=0,
+ ),
+ )
+
def resource_digests(
self,
resource_id,
diff --git a/pcs_test/tools/command_env/mock_push_cib.py b/pcs_test/tools/command_env/mock_push_cib.py
index 186d14f69..67275d0e6 100644
--- a/pcs_test/tools/command_env/mock_push_cib.py
+++ b/pcs_test/tools/command_env/mock_push_cib.py
@@ -8,19 +8,28 @@ class Call:
type = CALL_TYPE_PUSH_CIB
def __init__(
- self, cib_xml, custom_cib=False, wait_timeout=-1, exception=None
+ self,
+ cib_xml,
+ custom_cib=False,
+ wait_timeout=-1,
+ exception=None,
+ with_status=False,
):
self.cib_xml = cib_xml
self.custom_cib = custom_cib
self.wait_timeout = wait_timeout
self.exception = exception
+ self.with_status = with_status
def __repr__(self):
- return str("<CibPush wait_timeout='{0}'>").format(self.wait_timeout)
+ return (
+ f"<CibPush wait_timeout='{self.wait_timeout}' "
+ f"with_status='{self.with_status}'>"
+ )
def get_push_cib(call_queue):
- def push_cib(lib_env, custom_cib=None, wait_timeout=-1):
+ def push_cib(lib_env, custom_cib=None, wait_timeout=-1, with_status=False):
i, expected_call = call_queue.take(CALL_TYPE_PUSH_CIB)
if custom_cib is None and expected_call.custom_cib:
@@ -62,6 +71,21 @@ def get_push_cib(call_queue):
expected_type=type(expected_call.wait_timeout),
)
)
+ if with_status != expected_call.with_status:
+ raise AssertionError(
+ (
+ "Trying to call env.push_cib (call no. {index}) with "
+ "'with_status' == {real_value} ({real_type}) but it was "
+ "expected 'with_status' == {expected_value} "
+ "({expected_type})"
+ ).format(
+ index=i,
+ real_value=with_status,
+ real_type=type(with_status),
+ expected_value=expected_call.with_status,
+ expected_type=type(expected_call.with_status),
+ )
+ )
if expected_call.exception:
raise expected_call.exception
--
2.54.0

View File

@ -1,52 +0,0 @@
From 6142961fe0e39bdbba0d70f792fc27fb2bc096ba Mon Sep 17 00:00:00 2001
From: Ivan Devat <idevat@redhat.com>
Date: Thu, 7 Mar 2024 16:51:13 +0100
Subject: [PATCH] stop sending http headers to ruby part of pcsd
---
pcs/daemon/ruby_pcsd.py | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/pcs/daemon/ruby_pcsd.py b/pcs/daemon/ruby_pcsd.py
index 4b3b0ea1..e07e17cc 100644
--- a/pcs/daemon/ruby_pcsd.py
+++ b/pcs/daemon/ruby_pcsd.py
@@ -87,13 +87,34 @@ class RubyDaemonRequest(
http_request: HTTPServerRequest = None,
payload=None,
):
- headers = http_request.headers if http_request else HTTPHeaders()
+ # Headers from request are not propagated to ruby part. Ruby part doesn't
+ # work with standard headers in any special way. So, we send only path,
+ # method, query, body and special headers for communication between
+ # python part and ruby part. Tornado then adds necessary default
+ # headers. The motivation here is to prevent processing potentially
+ # maliciously crafted headers by rack.
+ headers = HTTPHeaders()
headers.add("X-Pcsd-Type", request_type)
if payload:
headers.add(
"X-Pcsd-Payload",
b64encode(json.dumps(payload).encode()).decode(),
)
+ if http_request:
+ for key, val in http_request.headers.get_all():
+ # From webui, POST request can come with either
+ # application/x-www-form-urlencoded or application/json content
+ # type. When we remove original HTTP headers, content type is
+ # added by tornado. But in the case of original application/json,
+ # tornado puts application/x-www-form-urlencoded there. To fix
+ # this let's keep the original header here in this case.
+ #
+ # The token, CIB_user and CIB_user_groups are transferred by the
+ # "Cookie" header and these information are evaluated in ruby.
+ if (
+ key.lower() == "content-type" and val == "application/json"
+ ) or key.lower() == "cookie":
+ headers.add(key, val)
return super(RubyDaemonRequest, cls).__new__(
cls,
request_type,
--
2.47.0

View File

@ -1,45 +0,0 @@
From 0ad47ec40b7a9a2cb6bdbdf11e1e5b3c59f49b8b Mon Sep 17 00:00:00 2001
From: Miroslav Lisik <mlisik@redhat.com>
Date: Tue, 20 May 2025 16:34:18 +0200
Subject: [PATCH] support for query limits in rack
---
pcsd/conf/pcsd | 6 ++++++
pcsd/pcsd.rb | 5 +++++
2 files changed, 11 insertions(+)
diff --git a/pcsd/conf/pcsd b/pcsd/conf/pcsd
index 98df4744..65a9c9a9 100644
--- a/pcsd/conf/pcsd
+++ b/pcsd/conf/pcsd
@@ -45,5 +45,11 @@ PCSD_SESSION_LIFETIME=3600
# is 50 (even if set lower).
PCSD_RESTART_AFTER_REQUESTS=200
+# These environment variables set the maximum query string bytesize and the
+# maximum number of query parameters that pcsd will attempt to parse.
+# See CVE-2025-46727 for details.
+#RACK_QUERY_PARSER_BYTESIZE_LIMIT=4194304
+#RACK_QUERY_PARSER_PARAMS_LIMIT=4096
+
# Do not change
RACK_ENV=production
diff --git a/pcsd/pcsd.rb b/pcsd/pcsd.rb
index 11698f54..a2634e4e 100644
--- a/pcsd/pcsd.rb
+++ b/pcsd/pcsd.rb
@@ -90,6 +90,11 @@ configure do
CAPABILITIES_PCSD = capabilities_pcsd.freeze
end
+error Rack::QueryParser::QueryLimitError do
+ $logger.warn(env['sinatra.error'].message)
+ return 400, env['sinatra.error'].message
+end
+
def run_cfgsync
node_connected = true
if Cfgsync::ConfigSyncControl.sync_thread_allowed?()
--
2.49.0

View File

@ -1,53 +1,38 @@
From 854efcf148c82e5a5e4f0afd71cc3333ea4a8ce4 Mon Sep 17 00:00:00 2001 From 20140bbe7ac508ae38dea9ff5dd6fa92ec84b8bf Mon Sep 17 00:00:00 2001
From: Ivan Devat <idevat@redhat.com> From: Ivan Devat <idevat@redhat.com>
Date: Tue, 20 Nov 2018 15:03:56 +0100 Date: Tue, 20 Nov 2018 15:03:56 +0100
Subject: [PATCH 1/2] do not support cluster setup with udp(u) transport Subject: [PATCH] do not support cluster setup with udp(u) transport in RHEL9
--- ---
pcs/pcs.8.in | 2 ++ pcs/pcs.8.in | 2 ++
pcs/usage.py | 1 + pcs/usage.py | 1 +
pcsd/public/css/style.css | 3 +++ 2 files changed, 3 insertions(+)
3 files changed, 6 insertions(+)
diff --git a/pcs/pcs.8.in b/pcs/pcs.8.in diff --git a/pcs/pcs.8.in b/pcs/pcs.8.in
index d504e8b4..93202d05 100644 index 3b644d054..a1c573cf5 100644
--- a/pcs/pcs.8.in --- a/pcs/pcs.8.in
+++ b/pcs/pcs.8.in +++ b/pcs/pcs.8.in
@@ -438,6 +438,8 @@ By default, encryption is enabled with cipher=aes256 and hash=sha256. To disable @@ -479,6 +479,8 @@ By default, encryption is enabled with cipher=aes256 and hash=sha256. To disable
Transports udp and udpu: Transports udp and udpu:
.br .br
+WARNING: These transports are not supported in RHEL 8. +WARNING: These transports are not supported in RHEL 9.
+.br +.br
These transports are limited to one address per node. They do not support traffic encryption nor compression. These transports are limited to one address per node. They do not support traffic encryption nor compression.
.br .br
Transport options are: ip_version, netmtu Transport options are: ip_version, netmtu
diff --git a/pcs/usage.py b/pcs/usage.py diff --git a/pcs/usage.py b/pcs/usage.py
index f4b84202..ee10370a 100644 index 4bcef3d25..d6eb51750 100644
--- a/pcs/usage.py --- a/pcs/usage.py
+++ b/pcs/usage.py +++ b/pcs/usage.py
@@ -1038,6 +1038,7 @@ Commands: @@ -1502,6 +1502,7 @@ Commands:
hash=sha256. To disable encryption, set cipher=none and hash=none. hash=sha256. To disable encryption, set cipher=none and hash=none.
Transports udp and udpu: Transports udp and udpu:
+ WARNING: These transports are not supported in RHEL 8. + WARNING: These transports are not supported in RHEL 9.
These transports are limited to one address per node. They do not These transports are limited to one address per node. They do not
support traffic encryption nor compression. support traffic encryption nor compression.
Transport options are: Transport options are:
diff --git a/pcsd/public/css/style.css b/pcsd/public/css/style.css
index 2f26e831..a7702ac4 100644
--- a/pcsd/public/css/style.css
+++ b/pcsd/public/css/style.css
@@ -949,6 +949,9 @@ table.args-table td.reg {
width: 6ch;
text-align: right;
}
+#csetup-transport .transport-types {
+ display: none;
+}
#csetup-transport-options.udp .knet-only,
#csetup-transport-options.knet .without-knet
{
-- --
2.43.0 2.54.0

File diff suppressed because it is too large Load Diff