Compare commits
14 Commits
bundle_nex
...
bundle
| Author | SHA256 | Date | |
|---|---|---|---|
|
a0f24b2e44
|
|||
|
a52c5a84f5
|
|||
|
aa70523380
|
|||
|
6391ebfedc
|
|||
|
|
fca15a9979 | ||
| 98722f7422 | |||
|
a564c87732
|
|||
| f0b919b9eb | |||
|
3b5688587a
|
|||
| d724ea46a7 | |||
|
08df922270
|
|||
|
f673ca273e
|
|||
| 9c78e0d1ef | |||
|
a8dda26a96
|
@@ -1 +1 @@
|
||||
3084db7285ade6f5552ea3e79c11d57a17a6b529
|
||||
720580e5796703426c70d97b2db1487a3d504349
|
||||
26
add-python3.11-as-preferable-for-salt-ssh-to-avoid-t.patch
Normal file
26
add-python3.11-as-preferable-for-salt-ssh-to-avoid-t.patch
Normal file
@@ -0,0 +1,26 @@
|
||||
From ea87110248948eb61628cda607ae1af34e83fdfe Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <vzhestkov@suse.com>
|
||||
Date: Wed, 1 Oct 2025 14:19:27 +0200
|
||||
Subject: [PATCH] Add python3.11 as preferable for salt-ssh to avoid
|
||||
tests fails
|
||||
|
||||
---
|
||||
salt/client/ssh/__init__.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py
|
||||
index 1d8426b7c2..bfb7d3d1d1 100644
|
||||
--- a/salt/client/ssh/__init__.py
|
||||
+++ b/salt/client/ssh/__init__.py
|
||||
@@ -157,7 +157,7 @@ SSH_PY_CODE='import base64;
|
||||
if [ -n "$DEBUG" ]
|
||||
then set -x
|
||||
fi
|
||||
-PYTHON_CMDS="/var/tmp/venv-salt-minion/bin/python python3 /usr/libexec/platform-python python27 python2.7 python26 python2.6 python2 python"
|
||||
+PYTHON_CMDS="/var/tmp/venv-salt-minion/bin/python python3.11 python3 /usr/libexec/platform-python python27 python2.7 python26 python2.6 python2 python"
|
||||
for py_cmd in $PYTHON_CMDS
|
||||
do
|
||||
if command -v "$py_cmd" >/dev/null 2>&1 && "$py_cmd" -c "import sys; sys.exit(not (sys.version_info >= (2, 6)));"
|
||||
--
|
||||
2.51.0
|
||||
|
||||
76
allow-libgit2-to-guess-sysdir-homedir-successfully-b.patch
Normal file
76
allow-libgit2-to-guess-sysdir-homedir-successfully-b.patch
Normal file
@@ -0,0 +1,76 @@
|
||||
From 6a57e821f3e16981c01078dc7e928672a6f77b88 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
|
||||
<psuarezhernandez@suse.com>
|
||||
Date: Mon, 6 Oct 2025 09:34:17 +0100
|
||||
Subject: [PATCH] Allow libgit2 to guess sysdir homedir successfully
|
||||
(bsc#1250520) (bsc#1227207) (#731)
|
||||
|
||||
* Allow libgit2 to guess sysdir homedir successfully
|
||||
|
||||
This prevents the generic error:
|
||||
|
||||
_pygit2.GitError: error loading known_hosts:
|
||||
|
||||
which is happening in certain pygit2/libgit2 versions
|
||||
|
||||
* Fix pygit2 unit test to check HOME is injected
|
||||
---
|
||||
salt/utils/gitfs.py | 19 ++++++++++++-------
|
||||
tests/pytests/unit/utils/test_gitfs.py | 4 +++-
|
||||
2 files changed, 15 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/salt/utils/gitfs.py b/salt/utils/gitfs.py
|
||||
index 2a8ecf1d0cb..d597c17b870 100644
|
||||
--- a/salt/utils/gitfs.py
|
||||
+++ b/salt/utils/gitfs.py
|
||||
@@ -110,6 +110,15 @@ try:
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
+ if "HOME" not in os.environ:
|
||||
+ # Make sure $HOME env variable is set before importing pygit2 to prevent
|
||||
+ # _pygit2.GitError: error loading known_hosts in some libgit2 versions.
|
||||
+ # The internal "git_sysdir__dirs" from libgit2, is initializated
|
||||
+ # when importing pygit2. The $HOME env must be present to allow libgit2
|
||||
+ # guessing function to successfully set the homedir in the initializated
|
||||
+ # libgit2 stack.
|
||||
+ # https://github.com/saltstack/salt/issues/64121
|
||||
+ os.environ["HOME"] = os.path.expanduser("~")
|
||||
import pygit2
|
||||
PYGIT2_VERSION = Version(pygit2.__version__)
|
||||
LIBGIT2_VERSION = Version(pygit2.LIBGIT2_VERSION)
|
||||
@@ -1890,13 +1899,9 @@ class Pygit2(GitProvider):
|
||||
"""
|
||||
# https://github.com/libgit2/pygit2/issues/339
|
||||
# https://github.com/libgit2/libgit2/issues/2122
|
||||
- # https://github.com/saltstack/salt/issues/64121
|
||||
- home = os.path.expanduser("~")
|
||||
- if "HOME" not in os.environ:
|
||||
- # Make sure $HOME env variable is set to prevent
|
||||
- # _pygit2.GitError: error loading known_hosts in some libgit2 versions.
|
||||
- os.environ["HOME"] = home
|
||||
- pygit2.settings.search_path[pygit2.GIT_CONFIG_LEVEL_GLOBAL] = home
|
||||
+ pygit2.settings.search_path[pygit2.GIT_CONFIG_LEVEL_GLOBAL] = (
|
||||
+ os.path.expanduser("~")
|
||||
+ )
|
||||
new = False
|
||||
if not os.listdir(self._cachedir):
|
||||
# Repo cachedir is empty, initialize a new repo there
|
||||
diff --git a/tests/pytests/unit/utils/test_gitfs.py b/tests/pytests/unit/utils/test_gitfs.py
|
||||
index baedd9fd708..4ab8e7735f0 100644
|
||||
--- a/tests/pytests/unit/utils/test_gitfs.py
|
||||
+++ b/tests/pytests/unit/utils/test_gitfs.py
|
||||
@@ -251,7 +251,9 @@ def test_checkout_pygit2_with_home_env_unset(_prepare_provider):
|
||||
provider.credentials = None
|
||||
with patched_environ(__cleanup__=["HOME"]):
|
||||
assert "HOME" not in os.environ
|
||||
- provider.init_remote()
|
||||
+ import importlib
|
||||
+
|
||||
+ importlib.reload(salt.utils.gitfs)
|
||||
assert "HOME" in os.environ
|
||||
|
||||
|
||||
--
|
||||
2.51.0
|
||||
|
||||
1268
backport-3006.17-security-fixes-739.patch
Normal file
1268
backport-3006.17-security-fixes-739.patch
Normal file
File diff suppressed because it is too large
Load Diff
36
do-not-break-signature-verification-on-latest-m2cryp.patch
Normal file
36
do-not-break-signature-verification-on-latest-m2cryp.patch
Normal file
@@ -0,0 +1,36 @@
|
||||
From 002a58144563a15034f982b19ba851326535570a Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
|
||||
<psuarezhernandez@suse.com>
|
||||
Date: Wed, 29 Oct 2025 10:30:58 +0000
|
||||
Subject: [PATCH] Do not break signature verification on latest
|
||||
M2Crypto versions (bsc#1251776)
|
||||
|
||||
---
|
||||
salt/crypt.py | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/salt/crypt.py b/salt/crypt.py
|
||||
index 981f633d51f..29fd159b48c 100644
|
||||
--- a/salt/crypt.py
|
||||
+++ b/salt/crypt.py
|
||||
@@ -243,7 +243,7 @@ def sign_message(privkey_path, message, passphrase=None):
|
||||
md = EVP.MessageDigest("sha1")
|
||||
md.update(salt.utils.stringutils.to_bytes(message))
|
||||
digest = md.final()
|
||||
- return key.sign(digest)
|
||||
+ return key.sign(digest, algo="sha1")
|
||||
else:
|
||||
signer = PKCS1_v1_5.new(key)
|
||||
return signer.sign(SHA.new(salt.utils.stringutils.to_bytes(message)))
|
||||
@@ -262,7 +262,7 @@ def verify_signature(pubkey_path, message, signature):
|
||||
md.update(salt.utils.stringutils.to_bytes(message))
|
||||
digest = md.final()
|
||||
try:
|
||||
- return pubkey.verify(digest, signature)
|
||||
+ return pubkey.verify(digest, signature, algo="sha1")
|
||||
except RSA.RSAError as exc:
|
||||
log.debug("Signature verification failed: %s", exc.args[0])
|
||||
return False
|
||||
--
|
||||
2.51.1
|
||||
|
||||
65
even-more-reliable-pillar-timeout-test.patch
Normal file
65
even-more-reliable-pillar-timeout-test.patch
Normal file
@@ -0,0 +1,65 @@
|
||||
From dc3027bab4925228cacde00ae626bf651d0a0c3b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
|
||||
<psuarezhernandez@suse.com>
|
||||
Date: Wed, 17 Sep 2025 09:56:44 +0200
|
||||
Subject: [PATCH] Even more reliable pillar timeout test
|
||||
|
||||
* Even more reliable pillar timeout test
|
||||
|
||||
* Use sys.executable on test_pillar_timeout test
|
||||
|
||||
---------
|
||||
|
||||
Co-authored-by: Daniel A. Wozniak <dwozniak@vmware.com>
|
||||
---
|
||||
.../integration/minion/test_return_retries.py | 18 +++++++++++-------
|
||||
1 file changed, 11 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/tests/pytests/integration/minion/test_return_retries.py b/tests/pytests/integration/minion/test_return_retries.py
|
||||
index 45dea9c4c76..9b71bed58c5 100644
|
||||
--- a/tests/pytests/integration/minion/test_return_retries.py
|
||||
+++ b/tests/pytests/integration/minion/test_return_retries.py
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from saltfactories.utils import random_string
|
||||
|
||||
from tests.support.helpers import dedent
|
||||
+import salt.utils.files
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@@ -57,14 +58,13 @@ def test_publish_retry(salt_master, salt_minion_retry, salt_cli, salt_run_cli):
|
||||
|
||||
@pytest.mark.slow_test
|
||||
@pytest.mark.flaky(max_runs=4)
|
||||
-def test_pillar_timeout(salt_master_factory):
|
||||
- cmd = (
|
||||
- sys.executable
|
||||
- + ' -c "import time; time.sleep(4.8); print(\'{\\"foo\\": \\"bar\\"}\');"'
|
||||
- ).strip()
|
||||
+def test_pillar_timeout(salt_master_factory, tmp_path):
|
||||
+ with salt.utils.files.fopen(tmp_path / "script.py", "w") as fp:
|
||||
+ fp.write('print(\'{"foo": "bar"}\');\n')
|
||||
+
|
||||
master_overrides = {
|
||||
"ext_pillar": [
|
||||
- {"cmd_json": cmd},
|
||||
+ {"cmd_json": f"{sys.executable} {tmp_path / 'script.py'}"},
|
||||
],
|
||||
"auto_accept": True,
|
||||
"worker_threads": 3,
|
||||
@@ -110,7 +110,11 @@ def test_pillar_timeout(salt_master_factory):
|
||||
sls_tempfile = master.state_tree.base.temp_file(
|
||||
"{}.sls".format(sls_name), sls_contents
|
||||
)
|
||||
- with master.started(), minion1.started(), minion2.started(), minion3.started(), minion4.started(), sls_tempfile:
|
||||
+ with master.started(), minion1.started(), minion2.started(), minion3.started(), minion4.started(), (
|
||||
+ sls_tempfile
|
||||
+ ):
|
||||
+ with salt.utils.files.fopen(tmp_path / "script.py", "w") as fp:
|
||||
+ fp.write('import time; time.sleep(6); print(\'{"foo": "bang"}\');\n')
|
||||
proc = cli.run("state.sls", sls_name, minion_tgt="*")
|
||||
# At least one minion should have a Pillar timeout
|
||||
print(proc)
|
||||
--
|
||||
2.51.0
|
||||
|
||||
164
extend-fails-to-warnings-until-2027-742.patch
Normal file
164
extend-fails-to-warnings-until-2027-742.patch
Normal file
@@ -0,0 +1,164 @@
|
||||
From 85d5cab70fc36994427fb4d4ca483b09f55d28f7 Mon Sep 17 00:00:00 2001
|
||||
From: Marek Czernek <marek.czernek@suse.com>
|
||||
Date: Thu, 8 Jan 2026 09:17:14 +0100
|
||||
Subject: [PATCH] Extend fails to warnings until 2027 (#742)
|
||||
|
||||
---
|
||||
salt/_logging/handlers.py | 6 +++---
|
||||
salt/log/__init__.py | 2 +-
|
||||
salt/log/handlers/__init__.py | 2 +-
|
||||
salt/log/mixins.py | 2 +-
|
||||
salt/log/setup.py | 4 ++--
|
||||
salt/modules/aptpkg.py | 2 +-
|
||||
salt/modules/cassandra_mod.py | 2 +-
|
||||
salt/returners/cassandra_return.py | 2 +-
|
||||
salt/returners/django_return.py | 2 +-
|
||||
9 files changed, 12 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/salt/_logging/handlers.py b/salt/_logging/handlers.py
|
||||
index d8bc68a49db..6b1521d7915 100644
|
||||
--- a/salt/_logging/handlers.py
|
||||
+++ b/salt/_logging/handlers.py
|
||||
@@ -36,7 +36,7 @@ class TemporaryLoggingHandler(logging.NullHandler):
|
||||
|
||||
def __init__(self, level=logging.NOTSET, max_queue_size=10000):
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"Please stop using '{name}.TemporaryLoggingHandler'. "
|
||||
"'{name}.TemporaryLoggingHandler' will go away after "
|
||||
"{{date}}.".format(name=__name__),
|
||||
@@ -229,7 +229,7 @@ if sys.version_info < (3, 7):
|
||||
def __init__(self, queue): # pylint: disable=useless-super-delegation
|
||||
super().__init__(queue)
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"Please stop using '{name}.QueueHandler' and instead "
|
||||
"use 'logging.handlers.QueueHandler'. "
|
||||
"'{name}.QueueHandler' will go away after "
|
||||
@@ -287,7 +287,7 @@ else:
|
||||
def __init__(self, queue): # pylint: disable=useless-super-delegation
|
||||
super().__init__(queue)
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"Please stop using '{name}.QueueHandler' and instead "
|
||||
"use 'logging.handlers.QueueHandler'. "
|
||||
"'{name}.QueueHandler' will go away after "
|
||||
diff --git a/salt/log/__init__.py b/salt/log/__init__.py
|
||||
index 69bfa8ed15b..392fd1561dd 100644
|
||||
--- a/salt/log/__init__.py
|
||||
+++ b/salt/log/__init__.py
|
||||
@@ -24,7 +24,7 @@ from salt.log.setup import (
|
||||
from salt.utils.versions import warn_until_date
|
||||
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"Please stop using '{name}' and instead use 'salt._logging'. "
|
||||
"'{name}' will go away after {{date}}.".format(name=__name__),
|
||||
stacklevel=3,
|
||||
diff --git a/salt/log/handlers/__init__.py b/salt/log/handlers/__init__.py
|
||||
index 55cf10cdb78..f6f8102fd65 100644
|
||||
--- a/salt/log/handlers/__init__.py
|
||||
+++ b/salt/log/handlers/__init__.py
|
||||
@@ -12,7 +12,7 @@ from salt._logging.handlers import (
|
||||
from salt.utils.versions import warn_until_date
|
||||
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"Please stop using '{name}' and instead use 'salt._logging.handlers'. "
|
||||
"'{name}' will go away after {{date}}.".format(name=__name__),
|
||||
)
|
||||
diff --git a/salt/log/mixins.py b/salt/log/mixins.py
|
||||
index 65f5ed7f78a..4564ac225c3 100644
|
||||
--- a/salt/log/mixins.py
|
||||
+++ b/salt/log/mixins.py
|
||||
@@ -11,7 +11,7 @@ from salt.utils.versions import warn_until_date
|
||||
# pylint: enable=unused-import
|
||||
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"Please stop using '{name}' and instead use 'salt._logging.mixins'. "
|
||||
"'{name}' will go away after {{date}}.".format(name=__name__),
|
||||
)
|
||||
diff --git a/salt/log/setup.py b/salt/log/setup.py
|
||||
index f4c80b0f280..5435d6de88f 100644
|
||||
--- a/salt/log/setup.py
|
||||
+++ b/salt/log/setup.py
|
||||
@@ -21,7 +21,7 @@ from salt._logging.impl import set_log_record_factory as setLogRecordFactory
|
||||
from salt.utils.versions import warn_until_date
|
||||
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"Please stop using '{name}' and instead use 'salt._logging'. "
|
||||
"'{name}' will go away after {{date}}. Do note however that "
|
||||
"'salt._logging' is now considered a non public implementation "
|
||||
@@ -34,7 +34,7 @@ def _deprecated_warning(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"Please stop using 'salt.log.setup.{name}()' as it no longer does anything and "
|
||||
"will go away after {{date}}.".format(name=func.__qualname__),
|
||||
stacklevel=4,
|
||||
diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py
|
||||
index 8244c639e85..f7884d9ccde 100644
|
||||
--- a/salt/modules/aptpkg.py
|
||||
+++ b/salt/modules/aptpkg.py
|
||||
@@ -3215,7 +3215,7 @@ def expand_repo_def(**kwargs):
|
||||
NOT USABLE IN THE CLI
|
||||
"""
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"The pkg.expand_repo_def function is deprecated and set for removal "
|
||||
"after {date}. This is only unsed internally by the apt pkg state "
|
||||
"module. If that's not the case, please file an new issue requesting "
|
||||
diff --git a/salt/modules/cassandra_mod.py b/salt/modules/cassandra_mod.py
|
||||
index db9c8821920..660a1ff70dd 100644
|
||||
--- a/salt/modules/cassandra_mod.py
|
||||
+++ b/salt/modules/cassandra_mod.py
|
||||
@@ -45,7 +45,7 @@ def __virtual__():
|
||||
)
|
||||
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"The cassandra returner is broken and deprecated, and will be removed"
|
||||
" after {date}. Use the cassandra_cql returner instead",
|
||||
)
|
||||
diff --git a/salt/returners/cassandra_return.py b/salt/returners/cassandra_return.py
|
||||
index 5fcc00ee8ce..00c6748eaeb 100644
|
||||
--- a/salt/returners/cassandra_return.py
|
||||
+++ b/salt/returners/cassandra_return.py
|
||||
@@ -53,7 +53,7 @@ def __virtual__():
|
||||
if not HAS_PYCASSA:
|
||||
return False, "Could not import cassandra returner; pycassa is not installed."
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"The cassandra returner is broken and deprecated, and will be removed"
|
||||
" after {date}. Use the cassandra_cql returner instead",
|
||||
)
|
||||
diff --git a/salt/returners/django_return.py b/salt/returners/django_return.py
|
||||
index 474653f3831..46f5c8791e8 100644
|
||||
--- a/salt/returners/django_return.py
|
||||
+++ b/salt/returners/django_return.py
|
||||
@@ -57,7 +57,7 @@ __virtualname__ = "django"
|
||||
|
||||
def __virtual__():
|
||||
warn_until_date(
|
||||
- "20260101",
|
||||
+ "20270101",
|
||||
"The django returner is broken and deprecated, and will be removed"
|
||||
" after {date}.",
|
||||
)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
6674
fix-salt-for-python-3.11.patch
Normal file
6674
fix-salt-for-python-3.11.patch
Normal file
File diff suppressed because it is too large
Load Diff
290
fix-tls-and-x509-modules-for-older-cryptography-modu.patch
Normal file
290
fix-tls-and-x509-modules-for-older-cryptography-modu.patch
Normal file
@@ -0,0 +1,290 @@
|
||||
From 7f15657c26c4e5e9fabc72f4da2d9a91353d5d3a Mon Sep 17 00:00:00 2001
|
||||
From: Marek Czernek <marek.czernek@suse.com>
|
||||
Date: Tue, 11 Nov 2025 08:46:20 +0100
|
||||
Subject: [PATCH] Fix tls and x509 modules for older cryptography
|
||||
module (#737)
|
||||
|
||||
---
|
||||
salt/modules/tls.py | 73 +++++++++++++++++-------
|
||||
salt/modules/x509.py | 128 +++++++++++++++++++++++++++++++++++--------
|
||||
2 files changed, 158 insertions(+), 43 deletions(-)
|
||||
|
||||
diff --git a/salt/modules/tls.py b/salt/modules/tls.py
|
||||
index 9d29bd1e9b..4d7db87f93 100644
|
||||
--- a/salt/modules/tls.py
|
||||
+++ b/salt/modules/tls.py
|
||||
@@ -104,6 +104,7 @@ import logging
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
+import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
@@ -1594,6 +1595,9 @@ def create_pkcs12(ca_name, CN, passphrase="", cacert_path=None, replace=False):
|
||||
|
||||
salt '*' tls.create_pkcs12 test localhost
|
||||
"""
|
||||
+ # Necessary for OSes with older cryptography module
|
||||
+ compat_mode = sys.version_info < (3,12)
|
||||
+
|
||||
set_ca_path(cacert_path)
|
||||
p12_path = f"{cert_base_path()}/{ca_name}/certs/{CN}.p12"
|
||||
ca_cert_path = f"{cert_base_path()}/{ca_name}/{ca_name}_ca_cert.crt"
|
||||
@@ -1605,7 +1609,12 @@ def create_pkcs12(ca_name, CN, passphrase="", cacert_path=None, replace=False):
|
||||
|
||||
try:
|
||||
with salt.utils.files.fopen(ca_cert_path, "rb") as fhr:
|
||||
- ca_cert = cryptography.x509.load_pem_x509_certificate(fhr.read())
|
||||
+ if compat_mode:
|
||||
+ ca_cert = OpenSSL.crypto.load_certificate(
|
||||
+ OpenSSL.crypto.FILETYPE_PEM, fhr.read()
|
||||
+ )
|
||||
+ else:
|
||||
+ ca_cert = cryptography.x509.load_pem_x509_certificate(fhr.read())
|
||||
except OSError:
|
||||
return 'There is no CA named "{}"'.format(ca_name)
|
||||
except ValueError as e:
|
||||
@@ -1613,34 +1622,58 @@ def create_pkcs12(ca_name, CN, passphrase="", cacert_path=None, replace=False):
|
||||
|
||||
try:
|
||||
with salt.utils.files.fopen(cert_path, "rb") as fhr:
|
||||
- cert = cryptography.x509.load_pem_x509_certificate(fhr.read())
|
||||
+ if compat_mode:
|
||||
+ cert = OpenSSL.crypto.load_certificate(
|
||||
+ OpenSSL.crypto.FILETYPE_PEM, fhr.read()
|
||||
+ )
|
||||
+ else:
|
||||
+ cert = cryptography.x509.load_pem_x509_certificate(fhr.read())
|
||||
with salt.utils.files.fopen(priv_key_path, "rb") as fhr:
|
||||
- key = cryptography_serialization.load_pem_private_key(
|
||||
- fhr.read(),
|
||||
- password=None,
|
||||
- )
|
||||
+ if compat_mode:
|
||||
+ key = OpenSSL.crypto.load_privatekey(
|
||||
+ OpenSSL.crypto.FILETYPE_PEM, fhr.read()
|
||||
+ )
|
||||
+ else:
|
||||
+ key = cryptography_serialization.load_pem_private_key(
|
||||
+ fhr.read(),
|
||||
+ password=None,
|
||||
+ )
|
||||
except OSError:
|
||||
return 'There is no certificate that matches the CN "{}"'.format(CN)
|
||||
except ValueError as e:
|
||||
return f'Could not load certificate {cert_path}: {e}'
|
||||
|
||||
- if passphrase:
|
||||
- encryption_algorithm = cryptography_serialization.BestAvailableEncryption(
|
||||
- salt.utils.stringutils.to_bytes(passphrase)
|
||||
- )
|
||||
+ if compat_mode:
|
||||
+ pkcs12 = OpenSSL.crypto.PKCS12()
|
||||
+
|
||||
+ pkcs12.set_certificate(cert)
|
||||
+ pkcs12.set_ca_certificates([ca_cert])
|
||||
+ pkcs12.set_privatekey(key)
|
||||
+
|
||||
+ with salt.utils.files.fopen(
|
||||
+ "{}/{}/certs/{}.p12".format(cert_base_path(), ca_name, CN), "wb"
|
||||
+ ) as ofile:
|
||||
+ ofile.write(
|
||||
+ pkcs12.export(passphrase=salt.utils.stringutils.to_bytes(passphrase))
|
||||
+ )
|
||||
else:
|
||||
- encryption_algorithm = cryptography_serialization.NoEncryption()
|
||||
+ if passphrase:
|
||||
+ encryption_algorithm = cryptography_serialization.BestAvailableEncryption(
|
||||
+ salt.utils.stringutils.to_bytes(passphrase)
|
||||
+ )
|
||||
+ else:
|
||||
+ encryption_algorithm = cryptography_serialization.NoEncryption()
|
||||
|
||||
- pkcs12 = cryptography_pkcs12.serialize_key_and_certificates(
|
||||
- name=salt.utils.stringutils.to_bytes(CN),
|
||||
- key=key,
|
||||
- cert=cert,
|
||||
- cas=[ca_cert],
|
||||
- encryption_algorithm=encryption_algorithm,
|
||||
- )
|
||||
+ pkcs12 = cryptography_pkcs12.serialize_key_and_certificates(
|
||||
+ name=salt.utils.stringutils.to_bytes(CN),
|
||||
+ key=key,
|
||||
+ cert=cert,
|
||||
+ cas=[ca_cert],
|
||||
+ encryption_algorithm=encryption_algorithm,
|
||||
+ )
|
||||
|
||||
- with salt.utils.files.fopen(p12_path, "wb") as ofile:
|
||||
- ofile.write(pkcs12)
|
||||
+ with salt.utils.files.fopen(p12_path, "wb") as ofile:
|
||||
+ ofile.write(pkcs12)
|
||||
|
||||
return 'Created PKCS#12 Certificate for "{0}": "{1}/{2}/certs/{0}.p12"'.format(
|
||||
CN,
|
||||
diff --git a/salt/modules/x509.py b/salt/modules/x509.py
|
||||
index 164541fc76..373e394856 100644
|
||||
--- a/salt/modules/x509.py
|
||||
+++ b/salt/modules/x509.py
|
||||
@@ -32,16 +32,20 @@ import tempfile
|
||||
|
||||
import salt.exceptions
|
||||
import salt.utils.data
|
||||
-import salt.utils.dictupdate
|
||||
import salt.utils.files
|
||||
import salt.utils.path
|
||||
import salt.utils.platform
|
||||
import salt.utils.stringutils
|
||||
import salt.utils.versions
|
||||
-import salt.utils.x509 as x509util
|
||||
from salt.state import STATE_INTERNAL_KEYWORDS as _STATE_INTERNAL_KEYWORDS
|
||||
from salt.utils.odict import OrderedDict
|
||||
|
||||
+# Necessary for OSes with older cryptography module
|
||||
+COMPAT_MODE = sys.version_info < (3,12)
|
||||
+if not COMPAT_MODE:
|
||||
+ import salt.utils.dictupdate
|
||||
+ import salt.utils.x509 as x509util
|
||||
+
|
||||
try:
|
||||
import M2Crypto
|
||||
|
||||
@@ -988,35 +992,113 @@ def create_crl(
|
||||
|
||||
if revoked is None:
|
||||
revoked = []
|
||||
+ if COMPAT_MODE:
|
||||
+ crl = OpenSSL.crypto.CRL()
|
||||
+ for rev_item in revoked:
|
||||
+ if "certificate" in rev_item:
|
||||
+ rev_cert = read_certificate(rev_item["certificate"])
|
||||
+ rev_item["serial_number"] = rev_cert["Serial Number"]
|
||||
+ rev_item["not_after"] = rev_cert["Not After"]
|
||||
|
||||
- for rev_item in revoked:
|
||||
- if "reason" in rev_item:
|
||||
- salt.utils.dictupdate.set_dict_key_value(
|
||||
- rev_item, "extensions:CRLReason", rev_item["reason"]
|
||||
+ serial_number = rev_item["serial_number"].replace(":", "")
|
||||
+ # OpenSSL bindings requires this to be a non-unicode string
|
||||
+ serial_number = salt.utils.stringutils.to_bytes(serial_number)
|
||||
+
|
||||
+ if "not_after" in rev_item and not include_expired:
|
||||
+ not_after = datetime.datetime.strptime(
|
||||
+ rev_item["not_after"], "%Y-%m-%d %H:%M:%S"
|
||||
+ )
|
||||
+ if datetime.datetime.now() > not_after:
|
||||
+ continue
|
||||
+
|
||||
+ if "revocation_date" not in rev_item:
|
||||
+ rev_item["revocation_date"] = datetime.datetime.now().strftime(
|
||||
+ "%Y-%m-%d %H:%M:%S"
|
||||
+ )
|
||||
+
|
||||
+ rev_date = datetime.datetime.strptime(
|
||||
+ rev_item["revocation_date"], "%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
+ rev_date = rev_date.strftime("%Y%m%d%H%M%SZ")
|
||||
+ rev_date = salt.utils.stringutils.to_bytes(rev_date)
|
||||
|
||||
- builder, private_key_obj = x509util.build_crl(
|
||||
- signing_private_key=signing_private_key,
|
||||
- signing_private_key_passphrase=signing_private_key_passphrase,
|
||||
- include_expired=include_expired,
|
||||
- revoked=revoked,
|
||||
- signing_cert=signing_cert,
|
||||
- days_valid=days_valid,
|
||||
- )
|
||||
+ rev = OpenSSL.crypto.Revoked()
|
||||
+ rev.set_serial(salt.utils.stringutils.to_bytes(serial_number))
|
||||
+ rev.set_rev_date(salt.utils.stringutils.to_bytes(rev_date))
|
||||
+
|
||||
+ if "reason" in rev_item:
|
||||
+ # Same here for OpenSSL bindings and non-unicode strings
|
||||
+ reason = salt.utils.stringutils.to_bytes(rev_item["reason"])
|
||||
+ rev.set_reason(reason)
|
||||
+
|
||||
+ crl.add_revoked(rev)
|
||||
+
|
||||
+ signing_cert = _text_or_file(signing_cert)
|
||||
+ cert = OpenSSL.crypto.load_certificate(
|
||||
+ OpenSSL.crypto.FILETYPE_PEM, get_pem_entry(signing_cert, pem_type="CERTIFICATE")
|
||||
+ )
|
||||
+ signing_private_key = _get_private_key_obj(
|
||||
+ signing_private_key, passphrase=signing_private_key_passphrase
|
||||
+ ).as_pem(cipher=None)
|
||||
+ key = OpenSSL.crypto.load_privatekey(
|
||||
+ OpenSSL.crypto.FILETYPE_PEM, get_pem_entry(signing_private_key)
|
||||
+ )
|
||||
+
|
||||
+ export_kwargs = {
|
||||
+ "cert": cert,
|
||||
+ "key": key,
|
||||
+ "type": OpenSSL.crypto.FILETYPE_PEM,
|
||||
+ "days": days_valid,
|
||||
+ }
|
||||
+ if digest:
|
||||
+ export_kwargs["digest"] = salt.utils.stringutils.to_bytes(digest)
|
||||
+ else:
|
||||
+ log.warning("No digest specified. The default md5 digest will be used.")
|
||||
+
|
||||
+ try:
|
||||
+ crltext = crl.export(**export_kwargs)
|
||||
+ except (TypeError, ValueError):
|
||||
+ log.warning(
|
||||
+ "Error signing crl with specified digest. Are you using "
|
||||
+ "pyopenssl 0.15 or newer? The default md5 digest will be used."
|
||||
+ )
|
||||
+ export_kwargs.pop("digest", None)
|
||||
+ crltext = crl.export(**export_kwargs)
|
||||
+
|
||||
+ if text:
|
||||
+ return crltext
|
||||
+
|
||||
+ return write_pem(text=crltext, path=path, pem_type="X509 CRL")
|
||||
|
||||
- if digest:
|
||||
- hashing_algorithm = x509util.get_hashing_algorithm(digest)
|
||||
else:
|
||||
- log.warning("No digest specified. The default md5 digest will be used.")
|
||||
- hashing_algorithm = x509util.get_hashing_algorithm("MD5")
|
||||
+ for rev_item in revoked:
|
||||
+ if "reason" in rev_item:
|
||||
+ salt.utils.dictupdate.set_dict_key_value(
|
||||
+ rev_item, "extensions:CRLReason", rev_item["reason"]
|
||||
+ )
|
||||
|
||||
- crl = builder.sign(private_key_obj, algorithm=hashing_algorithm)
|
||||
- crl_bytes = crl.public_bytes(x509util.serialization.Encoding.PEM)
|
||||
+ builder, private_key_obj = x509util.build_crl(
|
||||
+ signing_private_key=signing_private_key,
|
||||
+ signing_private_key_passphrase=signing_private_key_passphrase,
|
||||
+ include_expired=include_expired,
|
||||
+ revoked=revoked,
|
||||
+ signing_cert=signing_cert,
|
||||
+ days_valid=days_valid,
|
||||
+ )
|
||||
|
||||
- if text:
|
||||
- return crl_bytes.decode()
|
||||
+ if digest:
|
||||
+ hashing_algorithm = x509util.get_hashing_algorithm(digest)
|
||||
+ else:
|
||||
+ log.warning("No digest specified. The default md5 digest will be used.")
|
||||
+ hashing_algorithm = x509util.get_hashing_algorithm("MD5")
|
||||
|
||||
- return write_pem(text=crl_bytes, path=path, pem_type="X509 CRL")
|
||||
+ crl = builder.sign(private_key_obj, algorithm=hashing_algorithm)
|
||||
+ crl_bytes = crl.public_bytes(x509util.serialization.Encoding.PEM)
|
||||
+
|
||||
+ if text:
|
||||
+ return crl_bytes.decode()
|
||||
+
|
||||
+ return write_pem(text=crl_bytes, path=path, pem_type="X509 CRL")
|
||||
|
||||
|
||||
def sign_remote_certificate(argdic, **kwargs):
|
||||
--
|
||||
2.51.1
|
||||
|
||||
232
fixes-for-security-issues-cve-2025-13836-cve-2025-67.patch
Normal file
232
fixes-for-security-issues-cve-2025-13836-cve-2025-67.patch
Normal file
@@ -0,0 +1,232 @@
|
||||
From 324c7740438fd0bbcde1e0b6be70c92007c022ac Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <vzhestkov@suse.com>
|
||||
Date: Wed, 14 Jan 2026 14:08:48 +0100
|
||||
Subject: [PATCH] Fixes for security issues (CVE-2025-13836,
|
||||
CVE-2025-67725, CVE-2025-67726) (#744)
|
||||
|
||||
* Fixes for security issues (CVE-2025-67725)
|
||||
|
||||
httputil: Fix quadratic performance of repeated header lines
|
||||
|
||||
Previouisly, when many header lines with the same name were found
|
||||
in an HTTP request or response, repeated string concatenation would
|
||||
result in quadratic performance. This change does the concatenation
|
||||
lazily (with a cache) so that repeated headers can be processed
|
||||
efficiently.
|
||||
|
||||
Security: The previous behavior allowed a denial of service attack
|
||||
via a maliciously crafted HTTP message, but only if the
|
||||
max_header_size was increased from its default of 64kB.
|
||||
|
||||
* Patch tornado for (BDSA-2025-60811, CVE-2025-67726)
|
||||
|
||||
httputil: Fix quadratic behavior in _parseparam
|
||||
|
||||
Prior to this change, _parseparam had O(n^2) behavior when parsing
|
||||
certain inputs, which could be a DoS vector. This change adapts
|
||||
logic from the equivalent function in the python standard library
|
||||
in https://github.com/python/cpython/pull/136072/files
|
||||
|
||||
* Set a safe limit to http.client response read (CVE-2025-13836)
|
||||
|
||||
https://github.com/saltstack/salt/pull/68611
|
||||
|
||||
* Remove duplicated test
|
||||
|
||||
---------
|
||||
|
||||
Co-authored-by: Twangboy <shane.d.lee@gmail.com>
|
||||
Co-authored-by: Marek Czernek <marek.czernek@suse.com>
|
||||
---
|
||||
salt/ext/tornado/httputil.py | 56 ++++++++++++++++++--------
|
||||
salt/ext/tornado/test/httputil_test.py | 38 +++++++++++++++++
|
||||
salt/utils/nxos.py | 3 +-
|
||||
3 files changed, 79 insertions(+), 18 deletions(-)
|
||||
|
||||
diff --git a/salt/ext/tornado/httputil.py b/salt/ext/tornado/httputil.py
|
||||
index 4866b0c991..78953c5f6b 100644
|
||||
--- a/salt/ext/tornado/httputil.py
|
||||
+++ b/salt/ext/tornado/httputil.py
|
||||
@@ -139,8 +139,8 @@ class HTTPHeaders(MutableMapping):
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
- self._dict = {} # type: typing.Dict[str, str]
|
||||
self._as_list = {} # type: typing.Dict[str, typing.List[str]]
|
||||
+ self._combined_cache = {} # type: typing.Dict[str, str]
|
||||
self._last_key = None
|
||||
if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], HTTPHeaders):
|
||||
# Copy constructor
|
||||
@@ -158,9 +158,7 @@ class HTTPHeaders(MutableMapping):
|
||||
norm_name = _normalized_headers[name]
|
||||
self._last_key = norm_name
|
||||
if norm_name in self:
|
||||
- self._dict[norm_name] = (
|
||||
- native_str(self[norm_name]) + "," + native_str(value)
|
||||
- )
|
||||
+ self._combined_cache.pop(norm_name, None)
|
||||
self._as_list[norm_name].append(value)
|
||||
else:
|
||||
self[norm_name] = value
|
||||
@@ -193,7 +191,7 @@ class HTTPHeaders(MutableMapping):
|
||||
# continuation of a multi-line header
|
||||
new_part = " " + line.lstrip(HTTP_WHITESPACE)
|
||||
self._as_list[self._last_key][-1] += new_part
|
||||
- self._dict[self._last_key] += new_part
|
||||
+ self._combined_cache.pop(self._last_key, None)
|
||||
else:
|
||||
name, value = line.split(":", 1)
|
||||
self.add(name, value.strip(HTTP_WHITESPACE))
|
||||
@@ -216,23 +214,33 @@ class HTTPHeaders(MutableMapping):
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
norm_name = _normalized_headers[name]
|
||||
- self._dict[norm_name] = value
|
||||
+ self._combined_cache[norm_name] = value
|
||||
self._as_list[norm_name] = [value]
|
||||
|
||||
+ def __contains__(self, name):
|
||||
+ # This is an important optimization to avoid the expensive concatenation
|
||||
+ # in __getitem__ when it's not needed.
|
||||
+ if not isinstance(name, str):
|
||||
+ return False
|
||||
+ return name in self._as_list
|
||||
+
|
||||
def __getitem__(self, name):
|
||||
# type: (str) -> str
|
||||
- return self._dict[_normalized_headers[name]]
|
||||
+ header = _normalized_headers[name]
|
||||
+ if header not in self._combined_cache:
|
||||
+ self._combined_cache[header] = ",".join(self._as_list[header])
|
||||
+ return self._combined_cache[header]
|
||||
|
||||
def __delitem__(self, name):
|
||||
norm_name = _normalized_headers[name]
|
||||
- del self._dict[norm_name]
|
||||
+ del self._combined_cache[norm_name]
|
||||
del self._as_list[norm_name]
|
||||
|
||||
def __len__(self):
|
||||
- return len(self._dict)
|
||||
+ return len(self._as_list)
|
||||
|
||||
def __iter__(self):
|
||||
- return iter(self._dict)
|
||||
+ return iter(self._as_list)
|
||||
|
||||
def copy(self):
|
||||
# defined in dict but not in MutableMapping.
|
||||
@@ -894,19 +902,33 @@ def parse_response_start_line(line):
|
||||
# combinations of semicolons and double quotes.
|
||||
# It has also been modified to support valueless parameters as seen in
|
||||
# websocket extension negotiations.
|
||||
+#
|
||||
+# _parseparam has been further modified with the logic from
|
||||
+# https://github.com/python/cpython/pull/136072/files
|
||||
+# to avoid quadratic behavior when parsing semicolons in quoted strings.
|
||||
+#
|
||||
+# TODO: See if we can switch to email.message.Message for this functionality.
|
||||
+# This is the suggested replacement for the cgi.py module now that cgi has
|
||||
+# been removed from recent versions of Python. We need to verify that
|
||||
+# the email module is consistent with our existing behavior (and all relevant
|
||||
|
||||
|
||||
def _parseparam(s):
|
||||
- while s[:1] == ";":
|
||||
- s = s[1:]
|
||||
- end = s.find(";")
|
||||
- while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
|
||||
- end = s.find(";", end + 1)
|
||||
+ start = 0
|
||||
+ while s.find(";", start) == start:
|
||||
+ start += 1
|
||||
+ end = s.find(";", start)
|
||||
+ ind, diff = start, 0
|
||||
+ while end > 0:
|
||||
+ diff += s.count('"', ind, end) - s.count('\\"', ind, end)
|
||||
+ if diff % 2 == 0:
|
||||
+ break
|
||||
+ end, ind = ind, s.find(";", end + 1)
|
||||
if end < 0:
|
||||
end = len(s)
|
||||
- f = s[:end]
|
||||
+ f = s[start:end]
|
||||
yield f.strip()
|
||||
- s = s[end:]
|
||||
+ start = end
|
||||
|
||||
|
||||
def _parse_header(line):
|
||||
diff --git a/salt/ext/tornado/test/httputil_test.py b/salt/ext/tornado/test/httputil_test.py
|
||||
index c613b4e41a..bdbfaa2b6a 100644
|
||||
--- a/salt/ext/tornado/test/httputil_test.py
|
||||
+++ b/salt/ext/tornado/test/httputil_test.py
|
||||
@@ -245,6 +245,30 @@ Foo
|
||||
self.assertEqual(file["body"], b"Foo")
|
||||
|
||||
|
||||
+ def test_disposition_param_linear_performance(self):
|
||||
+ # This is a regression test for performance of parsing parameters
|
||||
+ # to the content-disposition header, specifically for semicolons within
|
||||
+ # quoted strings.
|
||||
+ def f(n):
|
||||
+ start = time.time()
|
||||
+ message = (
|
||||
+ b"--1234\r\nContent-Disposition: form-data; "
|
||||
+ + b'x="'
|
||||
+ + b";" * n
|
||||
+ + b'"; '
|
||||
+ + b'name="files"; filename="a.txt"\r\n\r\nFoo\r\n--1234--\r\n'
|
||||
+ )
|
||||
+ args: dict[str, list[bytes]] = {}
|
||||
+ files: dict[str, list[HTTPFile]] = {}
|
||||
+ parse_multipart_form_data(b"1234", message, args, files)
|
||||
+ return time.time() - start
|
||||
+
|
||||
+ d1 = f(1_000)
|
||||
+ d2 = f(10_000)
|
||||
+ if d2 / d1 > 20:
|
||||
+ self.fail(f"Disposition param parsing is not linear: {d1=} vs {d2=}")
|
||||
+
|
||||
+
|
||||
class HTTPHeadersTest(unittest.TestCase):
|
||||
def test_multi_line(self):
|
||||
# Lines beginning with whitespace are appended to the previous line
|
||||
@@ -367,6 +391,20 @@ Foo: even
|
||||
headers2 = HTTPHeaders.parse(str(headers))
|
||||
self.assertEquals(headers, headers2)
|
||||
|
||||
+ def test_linear_performance(self):
|
||||
+ def f(n):
|
||||
+ start = time.time()
|
||||
+ headers = HTTPHeaders()
|
||||
+ for i in range(n):
|
||||
+ headers.add("X-Foo", "bar")
|
||||
+ return time.time() - start
|
||||
+
|
||||
+ # This runs under 50ms on my laptop as of 2025-12-09.
|
||||
+ d1 = f(10000)
|
||||
+ d2 = f(100000)
|
||||
+ if d2 / d1 > 20:
|
||||
+ # d2 should be about 10x d1 but allow a wide margin for variability.
|
||||
+ self.fail("HTTPHeaders.add() does not scale linearly: %s vs %s" % (d1, d2))
|
||||
|
||||
class FormatTimestampTest(unittest.TestCase):
|
||||
# Make sure that all the input types are supported.
|
||||
diff --git a/salt/utils/nxos.py b/salt/utils/nxos.py
|
||||
index 2572a76267..654290155e 100644
|
||||
--- a/salt/utils/nxos.py
|
||||
+++ b/salt/utils/nxos.py
|
||||
@@ -212,7 +212,8 @@ class NxapiClient:
|
||||
body = response
|
||||
|
||||
if self.nxargs["connect_over_uds"]:
|
||||
- body = json.loads(response.read().decode("utf-8"))
|
||||
+ max_safe_read = 10 * 1024 * 1024
|
||||
+ body = json.loads(response.read(max_safe_read).decode("utf-8"))
|
||||
|
||||
# Proceed with caution. The JSON may not be complete.
|
||||
# Don't just return body['ins_api']['outputs']['output'] directly.
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@@ -40,7 +40,6 @@ saltbundlepy-requests.deb$
|
||||
saltbundlepy-setuptools.deb$
|
||||
saltbundlepy-six.deb$
|
||||
saltbundlepy-urllib3.deb$
|
||||
saltbundlepy-apt.deb$
|
||||
|
||||
saltbundlepy-docker.deb$
|
||||
saltbundlepy-docker-pycreds.deb$
|
||||
|
||||
1209
modify-readme-for-opensuse-728.patch
Normal file
1209
modify-readme-for-opensuse-728.patch
Normal file
File diff suppressed because it is too large
Load Diff
173
simplify-utils.json.find_json-function.patch
Normal file
173
simplify-utils.json.find_json-function.patch
Normal file
@@ -0,0 +1,173 @@
|
||||
From 7cbb68f36824161743f4cc60d8920e2cea039e5e Mon Sep 17 00:00:00 2001
|
||||
From: Marek Czernek <marek.czernek@suse.com>
|
||||
Date: Fri, 9 Jan 2026 16:49:19 +0100
|
||||
Subject: [PATCH] Simplify utils.json.find_json function
|
||||
|
||||
The previous implementation computed all combinations of potential JSON
|
||||
documents and tried to `json.loads()`them. That resumted in num({) *
|
||||
num(}) tries, which could take hours on large inputs.
|
||||
|
||||
The approach implemented with this change simplifies the work we do: we
|
||||
only look for opening '{' and '[' characters, and try to parse the rest
|
||||
of input string with JSONDecoder.raw_decode. This method ignores
|
||||
extraneous data at the end and is faster than doing it ourselves in
|
||||
Python.
|
||||
|
||||
Co-authored-by: Alexander Graul <agraul@suse.com>
|
||||
---
|
||||
changelog/68258.fixed.md | 1 +
|
||||
salt/utils/json.py | 80 ++++++---------------------
|
||||
tests/pytests/unit/utils/test_json.py | 5 --
|
||||
tests/unit/utils/test_json.py | 12 ++++
|
||||
4 files changed, 31 insertions(+), 67 deletions(-)
|
||||
create mode 100644 changelog/68258.fixed.md
|
||||
|
||||
diff --git a/changelog/68258.fixed.md b/changelog/68258.fixed.md
|
||||
new file mode 100644
|
||||
index 0000000000..a9afeccef7
|
||||
--- /dev/null
|
||||
+++ b/changelog/68258.fixed.md
|
||||
@@ -0,0 +1 @@
|
||||
+Simplied and sped up `utils.json.find_json` function
|
||||
diff --git a/salt/utils/json.py b/salt/utils/json.py
|
||||
index 26cb38cdbe..1605e75f9f 100644
|
||||
--- a/salt/utils/json.py
|
||||
+++ b/salt/utils/json.py
|
||||
@@ -2,7 +2,7 @@
|
||||
Functions to work with JSON
|
||||
"""
|
||||
|
||||
-
|
||||
+import contextlib
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -25,69 +25,25 @@ def __split(raw):
|
||||
return raw.splitlines()
|
||||
|
||||
|
||||
-def find_json(raw):
|
||||
- """
|
||||
- Pass in a raw string and load the json when it starts. This allows for a
|
||||
- string to start with garbage and end with json but be cleanly loaded
|
||||
- """
|
||||
- ret = {}
|
||||
- lines = __split(raw)
|
||||
- lengths = list(map(len, lines))
|
||||
- starts = []
|
||||
- ends = []
|
||||
-
|
||||
- # Search for possible starts end ends of the json fragments
|
||||
- for ind, _ in enumerate(lines):
|
||||
- line = lines[ind].lstrip()
|
||||
- line = line[0] if line else line
|
||||
- if line == "{" or line == "[":
|
||||
- starts.append((ind, line))
|
||||
- if line == "}" or line == "]":
|
||||
- ends.append((ind, line))
|
||||
-
|
||||
- # List all the possible pairs of starts and ends,
|
||||
- # and fill the length of each block to sort by size after
|
||||
- starts_ends = []
|
||||
- for start, start_br in starts:
|
||||
- for end, end_br in reversed(ends):
|
||||
- if end > start and (
|
||||
- (start_br == "{" and end_br == "}")
|
||||
- or (start_br == "[" and end_br == "]")
|
||||
- ):
|
||||
- starts_ends.append((start, end, sum(lengths[start : end + 1])))
|
||||
-
|
||||
- # Iterate through all the possible pairs starting from the largest
|
||||
- starts_ends.sort(key=lambda x: (x[2], x[1] - x[0], x[0]), reverse=True)
|
||||
- for start, end, _ in starts_ends:
|
||||
- working = "\n".join(lines[start : end + 1])
|
||||
- try:
|
||||
- ret = json.loads(working)
|
||||
- return ret
|
||||
- except ValueError:
|
||||
- pass
|
||||
- # Try filtering non-JSON text right after the last closing curly brace
|
||||
- end_str = lines[end].lstrip()[0]
|
||||
- working = "\n".join(lines[start : end]) + end_str
|
||||
- try:
|
||||
- ret = json.loads(working)
|
||||
- return ret
|
||||
- except ValueError:
|
||||
- continue
|
||||
+def find_json(s: str):
|
||||
+ """Pass in a string and load JSON within it.
|
||||
|
||||
- # Fall back to old implementation for backward compatibility
|
||||
- # excpecting json after the text
|
||||
- for ind, _ in enumerate(lines):
|
||||
- working = "\n".join(lines[ind:])
|
||||
- try:
|
||||
- ret = json.loads(working)
|
||||
- except ValueError:
|
||||
- continue
|
||||
- if ret:
|
||||
- return ret
|
||||
+ The string may contain non-JSON text before and after the JSON document.
|
||||
|
||||
- if not ret:
|
||||
- # Not json, raise an error
|
||||
- raise ValueError
|
||||
+ Raises ValueError if no valid JSON was found.
|
||||
+ """
|
||||
+ decoder = json.JSONDecoder()
|
||||
+
|
||||
+ # We look for the beginning of JSON objects / arrays and let raw_decode() handle
|
||||
+ # extraneous data at the end.
|
||||
+ for idx, char in enumerate(s):
|
||||
+ if char == "{" or char == "[":
|
||||
+ # JSONDecodeErrors are expected on stray '{'/'[' in the non-JSON part
|
||||
+ with contextlib.suppress(json.JSONDecodeError):
|
||||
+ data, _ = decoder.raw_decode(s[idx:])
|
||||
+ return data
|
||||
+
|
||||
+ raise ValueError
|
||||
|
||||
|
||||
def import_json():
|
||||
diff --git a/tests/pytests/unit/utils/test_json.py b/tests/pytests/unit/utils/test_json.py
|
||||
index 72b1023003..f7aed28b42 100644
|
||||
--- a/tests/pytests/unit/utils/test_json.py
|
||||
+++ b/tests/pytests/unit/utils/test_json.py
|
||||
@@ -107,11 +107,6 @@ def test_find_json():
|
||||
ret = salt.utils.json.find_json(garbage_around_json)
|
||||
assert ret == expected_ret
|
||||
|
||||
- # Now pre-pend small json and re-test
|
||||
- small_json_pre_json = f"{test_small_json}{test_sample_json}"
|
||||
- ret = salt.utils.json.find_json(small_json_pre_json)
|
||||
- assert ret == expected_ret
|
||||
-
|
||||
# Now post-pend small json and re-test
|
||||
small_json_post_json = f"{test_sample_json}{test_small_json}"
|
||||
ret = salt.utils.json.find_json(small_json_post_json)
|
||||
diff --git a/tests/unit/utils/test_json.py b/tests/unit/utils/test_json.py
|
||||
index 5ea409a705..f5dcc1f72d 100644
|
||||
--- a/tests/unit/utils/test_json.py
|
||||
+++ b/tests/unit/utils/test_json.py
|
||||
@@ -49,6 +49,18 @@ class JSONTestCase(TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
+ def test_find_json_unbalanced_brace_in_string(self):
|
||||
+ test_sample_json = '{"title": "I like curly braces like this one:{"}'
|
||||
+ expected_ret = {"title": "I like curly braces like this one:{"}
|
||||
+ ret = salt.utils.json.find_json(test_sample_json)
|
||||
+ self.assertDictEqual(ret, expected_ret)
|
||||
+
|
||||
+ def test_find_json_unbalanced_square_bracket_in_string(self):
|
||||
+ test_sample_json = '{"title": "I like square brackets like this one:["}'
|
||||
+ expected_ret = {"title": "I like square brackets like this one:["}
|
||||
+ ret = salt.utils.json.find_json(test_sample_json)
|
||||
+ self.assertDictEqual(ret, expected_ret)
|
||||
+
|
||||
def test_find_json(self):
|
||||
test_sample_json = """
|
||||
{
|
||||
--
|
||||
2.52.0
|
||||
|
||||
91
speedup-wheel-key.finger-call-bsc-1240532-713.patch
Normal file
91
speedup-wheel-key.finger-call-bsc-1240532-713.patch
Normal file
@@ -0,0 +1,91 @@
|
||||
From c4542e59844bce3a65726564fa364170c1fe7b8c Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <vzhestkov@suse.com>
|
||||
Date: Wed, 14 Jan 2026 14:12:44 +0100
|
||||
Subject: [PATCH] Speedup wheel key.finger call (bsc#1240532) (#713)
|
||||
|
||||
* Reduce the number of os.path.basename calls with key.finger
|
||||
|
||||
* Simplify and speedup salt.key.Key.name_match
|
||||
|
||||
* Avoid not needed printing while calling wheel from master
|
||||
|
||||
* Populate missing parts for clear_load
|
||||
|
||||
* Remove redundant events to be fired
|
||||
---
|
||||
salt/key.py | 20 ++++++++++----------
|
||||
salt/master.py | 11 ++++++++---
|
||||
2 files changed, 18 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/salt/key.py b/salt/key.py
|
||||
index b15b80eca3..8cd248bb8c 100644
|
||||
--- a/salt/key.py
|
||||
+++ b/salt/key.py
|
||||
@@ -491,16 +491,15 @@ class Key:
|
||||
ret = {}
|
||||
if "," in match and isinstance(match, str):
|
||||
match = match.split(",")
|
||||
+ if not isinstance(match, list):
|
||||
+ match = [match]
|
||||
for status, keys in matches.items():
|
||||
+ if match == ["*"] and keys:
|
||||
+ ret[status] = keys
|
||||
+ continue
|
||||
for key in salt.utils.data.sorted_ignorecase(keys):
|
||||
- if isinstance(match, list):
|
||||
- for match_item in match:
|
||||
- if fnmatch.fnmatch(key, match_item):
|
||||
- if status not in ret:
|
||||
- ret[status] = []
|
||||
- ret[status].append(key)
|
||||
- else:
|
||||
- if fnmatch.fnmatch(key, match):
|
||||
+ for match_item in match:
|
||||
+ if fnmatch.fnmatch(key, match_item):
|
||||
if status not in ret:
|
||||
ret[status] = []
|
||||
ret[status].append(key)
|
||||
@@ -543,12 +542,13 @@ class Key:
|
||||
for dir_ in key_dirs:
|
||||
if dir_ is None:
|
||||
continue
|
||||
- ret[os.path.basename(dir_)] = []
|
||||
+ base_dir = os.path.basename(dir_)
|
||||
+ ret[base_dir] = []
|
||||
try:
|
||||
for fn_ in salt.utils.data.sorted_ignorecase(os.listdir(dir_)):
|
||||
if not fn_.startswith("."):
|
||||
if os.path.isfile(os.path.join(dir_, fn_)):
|
||||
- ret[os.path.basename(dir_)].append(
|
||||
+ ret[base_dir].append(
|
||||
salt.utils.stringutils.to_unicode(fn_)
|
||||
)
|
||||
except OSError:
|
||||
diff --git a/salt/master.py b/salt/master.py
|
||||
index 09ce7d36a7..b9f009a028 100644
|
||||
--- a/salt/master.py
|
||||
+++ b/salt/master.py
|
||||
@@ -2093,12 +2093,17 @@ class ClearFuncs(TransportMethods):
|
||||
"tag": tag,
|
||||
"user": username,
|
||||
}
|
||||
-
|
||||
- self.event.fire_event(data, tagify([jid, "new"], "wheel"))
|
||||
+ clear_load.update(
|
||||
+ {
|
||||
+ "__jid__": jid,
|
||||
+ "__tag__": tag,
|
||||
+ "__user__": username,
|
||||
+ "print_event": clear_load.get("print_event", False),
|
||||
+ }
|
||||
+ )
|
||||
ret = self.wheel_.call_func(fun, full_return=True, **clear_load)
|
||||
data["return"] = ret["return"]
|
||||
data["success"] = ret["success"]
|
||||
- self.event.fire_event(data, tagify([jid, "ret"], "wheel"))
|
||||
return {"tag": tag, "data": data}
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
log.error("Exception occurred while introspecting %s: %s", fun, exc)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
3360
use-internal-salt.utils.pkg.deb-classes-instead-of-a.patch
Normal file
3360
use-internal-salt.utils.pkg.deb-classes-instead-of-a.patch
Normal file
File diff suppressed because it is too large
Load Diff
33
use-versioned-python-interpreter-for-salt-ssh.patch
Normal file
33
use-versioned-python-interpreter-for-salt-ssh.patch
Normal file
@@ -0,0 +1,33 @@
|
||||
From 1df479ec297e340bbe5f4913afce02f6c8427bd4 Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <vzhestkov@suse.com>
|
||||
Date: Mon, 6 Oct 2025 16:41:46 +0200
|
||||
Subject: [PATCH] Use versioned python interpreter for salt-ssh
|
||||
|
||||
---
|
||||
salt/client/ssh/__init__.py | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py
|
||||
index bfb7d3d1d1..86e4bcceb0 100644
|
||||
--- a/salt/client/ssh/__init__.py
|
||||
+++ b/salt/client/ssh/__init__.py
|
||||
@@ -157,7 +157,7 @@ SSH_PY_CODE='import base64;
|
||||
if [ -n "$DEBUG" ]
|
||||
then set -x
|
||||
fi
|
||||
-PYTHON_CMDS="/var/tmp/venv-salt-minion/bin/python python3.11 python3 /usr/libexec/platform-python python27 python2.7 python26 python2.6 python2 python"
|
||||
+PYTHON_CMDS="/var/tmp/venv-salt-minion/bin/python {{PY3XX_CMD}}python3 /usr/libexec/platform-python python27 python2.7 python26 python2.6 python2 python"
|
||||
for py_cmd in $PYTHON_CMDS
|
||||
do
|
||||
if command -v "$py_cmd" >/dev/null 2>&1 && "$py_cmd" -c "import sys; sys.exit(not (sys.version_info >= (2, 6)));"
|
||||
@@ -1533,6 +1533,7 @@ ARGS = {arguments}\n'''.format(
|
||||
SSH_PY_CODE=py_code_enc,
|
||||
HOST_PY_MAJOR=sys.version_info[0],
|
||||
SET_PATH=self.set_path,
|
||||
+ PY3XX_CMD=f"python3.{sys.version_info.minor} " if sys.version_info >= (3, 11) else "",
|
||||
)
|
||||
else:
|
||||
cmd = saltwinshell.gen_shim(py_code_enc)
|
||||
--
|
||||
2.51.0
|
||||
|
||||
@@ -1,3 +1,103 @@
|
||||
-------------------------------------------------------------------
|
||||
Wed Jan 14 14:25:41 UTC 2026 - Marek Czernek <marek.czernek@suse.com>
|
||||
|
||||
- Use internal deb classes instead of external aptsource lib
|
||||
* Drop dependency on saltbundlepy-apt
|
||||
- Speed up wheel key.finger call (bsc#1240532)
|
||||
- Add security patches (bsc#1254903,bsc#1254905,bsc#1254904)
|
||||
- Simplify and speed up utils.find_json function (bsc#1246130)
|
||||
|
||||
- Added:
|
||||
* use-internal-salt.utils.pkg.deb-classes-instead-of-a.patch
|
||||
* speedup-wheel-key.finger-call-bsc-1240532-713.patch
|
||||
* fixes-for-security-issues-cve-2025-13836-cve-2025-67.patch
|
||||
* simplify-utils.json.find_json-function.patch
|
||||
|
||||
- Modified:
|
||||
* include-deb
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Thu Jan 8 08:39:12 UTC 2026 - Marek Czernek <marek.czernek@suse.com>
|
||||
|
||||
- Extend warn_until period to 2027
|
||||
|
||||
- Added:
|
||||
* extend-fails-to-warnings-until-2027-742.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Nov 24 21:02:49 UTC 2025 - Alexander Graul <alexander.graul@suse.com>
|
||||
|
||||
- Add minimum_auth_version to enforce security (CVE-2025-62349)
|
||||
- Backport security fixes for vendored tornado
|
||||
* BDSA-2024-3438
|
||||
* BDSA-2024-3439
|
||||
* BDSA-2024-9026
|
||||
- Junos module yaml loader fix (CVE-2025-62348)
|
||||
|
||||
- Added:
|
||||
* backport-3006.17-security-fixes-739.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Tue Nov 11 08:03:59 UTC 2025 - Marek Czernek <marek.czernek@suse.com>
|
||||
|
||||
- Fix TLS and x509 modules for OSes with older cryptography module
|
||||
|
||||
- Added:
|
||||
* fix-tls-and-x509-modules-for-older-cryptography-modu.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Fri Nov 7 16:37:10 UTC 2025 - Pablo Suárez Hernández <pablo.suarezhernandez@suse.com>
|
||||
|
||||
- Fix Salt for Python > 3.11 (bsc#1252285) (bsc#1252244)
|
||||
* Use external tornado on Python > 3.11
|
||||
* Make tls and x509 to use python-cryptography
|
||||
* Remove usage of spwd
|
||||
|
||||
- Added:
|
||||
* fix-salt-for-python-3.11.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Wed Oct 29 10:38:04 UTC 2025 - Pablo Suárez Hernández <pablo.suarezhernandez@suse.com>
|
||||
|
||||
- Fix payload signature verification on Tumbleweed (bsc#1251776)
|
||||
|
||||
- Added:
|
||||
* do-not-break-signature-verification-on-latest-m2cryp.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Oct 6 14:54:30 UTC 2025 - Victor Zhestkov <vzhestkov@suse.com>
|
||||
|
||||
- Use versioned python interpreter for salt-ssh
|
||||
|
||||
- Added:
|
||||
* use-versioned-python-interpreter-for-salt-ssh.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Oct 6 08:40:19 UTC 2025 - Pablo Suárez Hernández <pablo.suarezhernandez@suse.com>
|
||||
|
||||
- Fix known_hosts error on gitfs (bsc#1250520) (bsc#1227207)
|
||||
|
||||
- Added:
|
||||
* allow-libgit2-to-guess-sysdir-homedir-successfully-b.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Wed Oct 1 12:46:17 UTC 2025 - Victor Zhestkov <vzhestkov@suse.com>
|
||||
|
||||
- Add python3.11 as preferable for salt-ssh to avoid tests fails
|
||||
|
||||
- Added:
|
||||
* add-python3.11-as-preferable-for-salt-ssh-to-avoid-t.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Wed Sep 17 08:16:34 UTC 2025 - Pablo Suárez Hernández <pablo.suarezhernandez@suse.com>
|
||||
|
||||
- Make test_pillar_timeout test more reliable
|
||||
- Modify README and other doc files for openSUSE
|
||||
|
||||
- Added:
|
||||
* even-more-reliable-pillar-timeout-test.patch
|
||||
* modify-readme-for-opensuse-728.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Aug 11 14:02:35 UTC 2025 - Victor Zhestkov <vzhestkov@suse.com>
|
||||
|
||||
|
||||
@@ -539,6 +539,44 @@ Patch177: fix-the-tests-failing-on-almalinux-10-and-other-clon.patch
|
||||
Patch178: fix-functional.states.test_user-for-sles-16-and-micr.patch
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/68247
|
||||
Patch179: improve-sl-micro-6.2-detection-with-grains.patch
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/728
|
||||
Patch180: modify-readme-for-opensuse-728.patch
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/68331
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/729
|
||||
Patch181: even-more-reliable-pillar-timeout-test.patch
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/730
|
||||
Patch182: add-python3.11-as-preferable-for-salt-ssh-to-avoid-t.patch
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/68366
|
||||
Patch183: allow-libgit2-to-guess-sysdir-homedir-successfully-b.patch
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/732
|
||||
Patch184: use-versioned-python-interpreter-for-salt-ssh.patch
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/735
|
||||
Patch185: do-not-break-signature-verification-on-latest-m2cryp.patch
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/736
|
||||
Patch186: fix-salt-for-python-3.11.patch
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/737
|
||||
Patch187: fix-tls-and-x509-modules-for-older-cryptography-modu.patch
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/issues/68377
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/issues/68379
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/issues/68383
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/issues/68467
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/issues/68469
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/739
|
||||
Patch188: backport-3006.17-security-fixes-739.patch
|
||||
# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/742
|
||||
Patch189: extend-fails-to-warnings-until-2027-742.patch
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/68253
|
||||
Patch190: simplify-utils.json.find_json-function.patch
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/68595
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/68611
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/tornadoweb/tornado/pull/3553
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/tornadoweb/tornado/commit/771472cfdaeebc0d89a9cc46e249f8891a6b29cd
|
||||
Patch191: fixes-for-security-issues-cve-2025-13836-cve-2025-67.patch
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/68251
|
||||
Patch192: speedup-wheel-key.finger-call-bsc-1240532-713.patch
|
||||
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/67956
|
||||
Patch193: use-internal-salt.utils.pkg.deb-classes-instead-of-a.patch
|
||||
|
||||
|
||||
### IMPORTANT: The line below is used as a snippet marker. Do not touch it.
|
||||
### SALT PATCHES LIST END
|
||||
@@ -605,10 +643,6 @@ BuildRequires: python
|
||||
Requires(post): policycoreutils
|
||||
%endif
|
||||
|
||||
%if 0%{?debian} || 0%{?raspbian} || 0%{?ubuntu}
|
||||
BuildRequires: saltbundlepy-apt
|
||||
%endif
|
||||
|
||||
# --- [venvjail - BuildRequires] END ---
|
||||
# --- [venvjail - Requires] START ---
|
||||
# --- [venvjail - Requires] END ---
|
||||
|
||||
Reference in New Issue
Block a user