Skip to content

fix: do not open listen events socket in webhook - #70158

Closed
CrazyHackGUT wants to merge 3751 commits into
saltstack:3006.xfrom
CrazyHackGUT:fix/mem-leak-webhook-engine
Closed

fix: do not open listen events socket in webhook#70158
CrazyHackGUT wants to merge 3751 commits into
saltstack:3006.xfrom
CrazyHackGUT:fix/mem-leak-webhook-engine

Conversation

@CrazyHackGUT

Copy link
Copy Markdown

What does this PR do?

Fixes a memory leak caused by using webhook engine by incorrect get_master_event parameters.

What issues does this PR fix or reference?

Memory leak in EventPublisher

Previous Behavior

get_master_event returns an object with opened pull-socket, which was not required for the publication of events, what causes memory leak in EventPublisher: EventPublisher sends a message for another process, but webhook doesn't even plan to read them.

New Behavior

get_master_event don't opens a pull-socket.

Merge requirements satisfied?

[NOTICE] Bug fixes or features added to Salt require tests.

Commits signed with GPG?

No

dwoz and others added 30 commits June 30, 2026 15:35
…-3007x

Refresh install/onedir/FAQ/non-root docs (3007.x)
…-3008x

Refresh install/onedir/FAQ/non-root docs (3008.x)
Forward-merge 3006.x fixes originating in PRs saltstack#66282, saltstack#68493, saltstack#68930,
saltstack#69442, saltstack#69478, saltstack#69614.

Conflicts resolved:
- doc/topics/development/salt_extensions.rst: took downstream form
- salt/crypt.py: forward-ported saltstack#69443 auth_retries outer-loop cap
- salt/transport/tcp.py: forward-ported saltstack#66282 concurrent
  publish_payload writes; adapted to 3007.x async def / await
- salt/transport/zeromq.py: forward-ported saltstack#69478 _set_zmq_heartbeat
  helper; preserved 3007.x raw-socket publisher (no ZMQStream)
- salt/utils/asynchronous.py: took HEAD (tornado>=6 asyncio interop)
- tests/pytests/unit/test_crypt.py: forward-ported four 3006.x
  regression tests (saltstack#68493 whitespace-drift, saltstack#68930 gen_signature
  clean_key, saltstack#69442 auth_retries cap); tornado adaptation
- tests/pytests/unit/utils/test_asynchronous.py: union of HelperA/
  HelperB tests + new 65702 SyncWrapper asyncio-loop regression test
- Lock files + workflow yamls: regenerated via pre-commit

DU resolved by removal:
- salt/returners/mysql.py
- tests/pytests/unit/returners/test_mysql.py
3006.x introduced tests/pytests/functional/transport/tcp/test_pub_server_stability.py
via saltstack#66282 using salt.ext.tornado.gen / .ioloop. 3007.x+ removed
salt.ext.tornado — adapt imports to plain tornado.gen / tornado.ioloop.
tornado.gen.coroutine-returned Futures remain awaitable from async def,
so no logic changes needed.
TCPPuller.handle_stream (merged forward from 3006.x #d4e2e075aa3)
called self.io_loop.spawn_callback() to invoke payload_handler. That
API only exists on tornado.ioloop.IOLoop; on 3007.x+ this class runs
under a raw asyncio loop (_UnixSelectorEventLoop), so package tests
saw 'EventPublisher: Exception occurred while handling stream:
_UnixSelectorEventLoop object has no attribute spawn_callback' on
every event and the salt-master service failed to reach a running
state (test_salt_downgrade_minion timed out at factory start).

Match 3008.x/master (salt/transport/tcp.py:1510):
    self.io_loop.create_task(self.payload_handler(body))

Fixes CI Rocky Linux 9 downgrade 3007.14 on PR saltstack#69622.
…-26-06-30

Merge forward 3006.x into 3007.x
When a prereq chain is set up such that one state prereq's another
state that itself prereq's a third state (e.g. state1 --prereq-->
state2 --prereq--> state3), the intermediate state's own prereq
check node was created without its own prereq requirements. This
allowed the intermediate state to be evaluated in test mode
independently of the tail of the chain, so any state that always
proposes changes in test mode (e.g. test.succeed_with_changes,
module.run) caused the head of the chain to run even when the tail
produced no changes.

When adding a new prereq requisite (_add_prereq) from a chunk that
already has its own prereq check node (because another state prereq's
it), also register a PREREQ edge from the requisite's prereq check
node into the chunk's own prereq check node. This ensures the
intermediate prereq check waits for the tail's prereq check outcome
before deciding whether to run in test mode.

Also confirms the pre-existing regression test saltstack#68438 now passes on
Windows and Linux.

Fixes saltstack#68438
When a prereq chain is set up such that one state prereq's another
state that itself prereq's a third state (e.g. state1 --prereq-->
state2 --prereq--> state3), the intermediate state's own prereq
check node was created without its own prereq requirements. This
allowed the intermediate state to be evaluated in test mode
independently of the tail of the chain, so any state that always
proposes changes in test mode (e.g. test.succeed_with_changes,
module.run) caused the head of the chain to run even when the tail
produced no changes.

When adding a new prereq requisite (_add_prereq) from a chunk that
already has its own prereq check node (because another state prereq's
it), also register a PREREQ edge from the requisite's prereq check
node into the chunk's own prereq check node. This ensures the
intermediate prereq check waits for the tail's prereq check outcome
before deciding whether to run in test mode.

Also confirms the pre-existing regression test saltstack#68438 now passes on
Windows and Linux.

Fixes saltstack#68438
Restore the ``Test Salt / Rocky Linux 9 unit zeromq 4`` CI job to green
after the 3006.x→3007.x→3008.x merge-forward chain pulled 3006.x-only
regression tests into 3007.x whose expectations don't match the 3007.x
runtime API surface.

- ``test_verify_master_accepts_cached_key_with_whitespace_drift``,
  ``test_verify_master_caches_clean_key_on_first_contact``,
  ``test_authenticate_caps_retry_loop_with_auth_retries_69442``,
  ``test_authenticate_default_does_not_cap_retry_loop_69442``: switch
  from the removed 3006.x ``crypt.gen_keys(pki_dir, name, keysize)``
  signature to the 3007.x ``crypt.write_keys(...)`` equivalent, and
  add ``keys.cache_driver`` to opts so ``AsyncAuth.__singleton_init__``
  can construct the keystore cache.

- ``test_gen_signature_signs_clean_key`` and
  ``test_gen_signature_signs_clean_key_trailing_newline``: skip on
  3007.x. The module-level ``salt.crypt.gen_signature`` was removed by
  the master-pki cache refactor; the replacement
  ``MasterKeys.gen_signature`` signs ``pub.public_bytes()`` from a key
  object rather than the raw file content, so the saltstack#68930 whitespace-
  drift bug the tests were written against is not reachable on 3007.x.

- ``test_maintenance_duration``: add ``eauth_tokens.cache_driver``,
  ``eauth_tokens.cluster_id``, and ``cluster_id`` to the test's opts
  dict. ``Maintenance._post_fork_init`` now constructs a long-lived
  ``LoadAuth`` (as part of the memory-leak fix that caches it across
  loop iterations); ``LoadAuth.__init__`` reads those keys.

- ``test_minion_manager_stop_unblocks_resolve_dns_69466``: assert on
  either ``io_loop.create_task`` or ``io_loop.add_callback`` being
  called once. 3007.x refactored ``MinionManager.stop()`` to use
  ``create_task`` instead of ``add_callback``; the 3006.x-origin test
  hard-coded the older form.

- ``test_event_unpack_with_SaltDeserializationError``: assert on the
  new debug-level "skipping malformed event (deserialization error)"
  message that the memory-leak hardening emits from
  ``SaltEvent._get_event`` instead of the pre-hardening
  ``log.error("Unable to deserialize received event")`` call the test
  originally targeted. The hardening intentionally demotes the log
  level so a single bad IPC frame cannot spam the operator log.
The test used a fixed time.sleep(2) after spawning the salt CLI before
sending SIGINT. On slow CI hosts (observed on Photon OS 5 Arm64, both
tcp(fips) and zeromq(fips) integration lanes) the CLI had not yet
published its job when the signal arrived. Its scripts._handle_signals
path then took the AttributeError/KeyError branch (no pub_data), emitted
only "Exiting gracefully on Ctrl-c", and skipped the "This job's jid is"
message the test asserts on.

Wait on the master's salt/job/*/new event via event_listener instead.
That guarantees pub_data is populated in the CLI process before we
interrupt it, so the jid-bearing signal-handler branch always runs.

Fixes flakiness in
tests/pytests/integration/cli/test_salt.py::test_interrupt_on_long_running_job
seen at:
https://github.com/saltstack/salt/actions/runs/28505613358/job/84502049475
https://github.com/saltstack/salt/actions/runs/28505613358/job/84502049337
Restore the ``Test Salt / Rocky Linux 9 unit zeromq 4`` CI job to green
after the 3006.x→3007.x→3008.x merge-forward chain pulled 3006.x-only
regression tests into 3007.x whose expectations don't match the 3007.x
runtime API surface.

- ``test_verify_master_accepts_cached_key_with_whitespace_drift``,
  ``test_verify_master_caches_clean_key_on_first_contact``,
  ``test_authenticate_caps_retry_loop_with_auth_retries_69442``,
  ``test_authenticate_default_does_not_cap_retry_loop_69442``: switch
  from the removed 3006.x ``crypt.gen_keys(pki_dir, name, keysize)``
  signature to the 3007.x ``crypt.write_keys(...)`` equivalent, and
  add ``keys.cache_driver`` to opts so ``AsyncAuth.__singleton_init__``
  can construct the keystore cache.

- ``test_gen_signature_signs_clean_key`` and
  ``test_gen_signature_signs_clean_key_trailing_newline``: skip on
  3007.x. The module-level ``salt.crypt.gen_signature`` was removed by
  the master-pki cache refactor; the replacement
  ``MasterKeys.gen_signature`` signs ``pub.public_bytes()`` from a key
  object rather than the raw file content, so the saltstack#68930 whitespace-
  drift bug the tests were written against is not reachable on 3007.x.

- ``test_maintenance_duration``: add ``eauth_tokens.cache_driver``,
  ``eauth_tokens.cluster_id``, and ``cluster_id`` to the test's opts
  dict. ``Maintenance._post_fork_init`` now constructs a long-lived
  ``LoadAuth`` (as part of the memory-leak fix that caches it across
  loop iterations); ``LoadAuth.__init__`` reads those keys.

- ``test_minion_manager_stop_unblocks_resolve_dns_69466``: assert on
  either ``io_loop.create_task`` or ``io_loop.add_callback`` being
  called once. 3007.x refactored ``MinionManager.stop()`` to use
  ``create_task`` instead of ``add_callback``; the 3006.x-origin test
  hard-coded the older form.

- ``test_event_unpack_with_SaltDeserializationError``: assert on the
  new debug-level "skipping malformed event (deserialization error)"
  message that the memory-leak hardening emits from
  ``SaltEvent._get_event`` instead of the pre-hardening
  ``log.error("Unable to deserialize received event")`` call the test
  originally targeted. The hardening intentionally demotes the log
  level so a single bad IPC frame cannot spam the operator log.
The test used a fixed time.sleep(2) after spawning the salt CLI before
sending SIGINT. On slow CI hosts (observed on Photon OS 5 Arm64, both
tcp(fips) and zeromq(fips) integration lanes) the CLI had not yet
published its job when the signal arrived. Its scripts._handle_signals
path then took the AttributeError/KeyError branch (no pub_data), emitted
only "Exiting gracefully on Ctrl-c", and skipped the "This job's jid is"
message the test asserts on.

Wait on the master's salt/job/*/new event via event_listener instead.
That guarantees pub_data is populated in the CLI process before we
interrupt it, so the jid-bearing signal-handler branch always runs.

Fixes flakiness in
tests/pytests/integration/cli/test_salt.py::test_interrupt_on_long_running_job
seen at:
https://github.com/saltstack/salt/actions/runs/28505613358/job/84502049475
https://github.com/saltstack/salt/actions/runs/28505613358/job/84502049337
…prereq-chain-dag

Fix prereq chain regression in nested prereq DAG setup (saltstack#68438)
Two bugs kept the Sync .lock files job from running for 3008.x PRs, leaving lock
files stale:

- on.pull_request.branches omitted 3008.x (it only listed master and 3006.x), so
  the workflow never triggered for PRs targeting 3008.x. Add all four release
  branches.
- The actor guard only matched 'dependabot', so it skipped whenever the
  salt-pr-bot rebase bot re-pushed a branch. Also fire for salt-pr-bot.
Two bugs kept the Sync .lock files job from running for 3007.x PRs, leaving lock
files stale:

- on.pull_request.branches omitted 3007.x (it only listed master and 3006.x), so
  the workflow never triggered for PRs targeting 3007.x. Add all four release
  branches.
- The actor guard only matched 'dependabot', so it skipped whenever the
  salt-pr-bot rebase bot re-pushed a branch. Also fire for salt-pr-bot.
…c-fix-3007.x

Fix dependabot lock-sync workflow triggering on 3007.x
…c-fix-3008.x

Fix dependabot lock-sync workflow triggering on 3008.x
…ltstack#69455)

The unmaintained `linode-python` 1.1.1 package targets the retired
Linode API v3 and uses `is not 0` / `is 1` against literals, which
Python 3.12+ emits as `SyntaxWarning` from `linode/api.py` lines
293, 348, and 356. On RHEL/Rocky/Oracle Linux 9.x, the salt-common
onedir's post-install scriptlet imports the onedir's Python which
in turn imports `linode-python` and the warnings surface during
every package install/upgrade.

Drop `linode-python` from `requirements/static/pkg/linux.txt` and
its dependent CI/pkg linux lockfiles so it is no longer installed
into the salt-common onedir. `salt.cloud.clouds.linode` already
uses the Linode APIv4 over HTTP/JSON (no `linode-python` import),
so the package is purely vestigial.

This backports saltstack#69339 (3006.x) and mirrors saltstack#68871 (master/3008.x)
to 3007.x.

Fixes saltstack#69455
The Fedora 40 skips on test_peer_communication and test_grains_remove_add
were added in 5a85699 (2024-05-17) as a temporary workaround
referencing saltstack#66539 and saltstack#66540 with no root cause captured. Fedora 40
reached end-of-life on 2025-05-13 and the tests now pass without the
workaround on 3007.x and master, so the skips can be dropped.

Fixes saltstack#66540
Fixes saltstack#66539
…69636)

Since 3008, pillar.get masks scalar string values by default, so signing
policies fetched from pillar came back as '**********' and certificate
generation failed with "Bad decrypt - is the password correct?".

Pass unmask=True so the policy retains its real values.

Fixes: saltstack#69253

(cherry picked from commit 27307c5)

Signed-off-by: Teddy Andrieux <teddy.andrieux@scality.com>
* fix timeout for salt-api

* Add changelog and regression test for salt-api timeout fix

Addresses @twangboy's CHANGES_REQUESTED on PR saltstack#62188.

- changelog/62187.fixed.md describes the salt-api hang fix.
- test_mk_token_missing_password_returns_empty pins the missing-password
  /username path: mk_token must return {} instead of raising
  SaltInvocationError, which previously escaped through the master
  clear-payload handler and hung salt-api workers for ~3 minutes per
  bad request.

Co-authored-by: carrysauce <carrysauce@users.noreply.github.com>

* Apply reviewer suggestions: separate try/except for format_call, rename changelog to PR number

- Give format_call its own try/except block catching SaltInvocationError
  with a descriptive debug message, as requested by @twangboy
- Rename changelog/62187.fixed.md to changelog/62188.fixed.md (PR number,
  not issue number)

---------

Co-authored-by: Alex Donec <alex.donec@pinely.com>
Co-authored-by: Daniel A. Wozniak <dwozniak@broadcom.com>
Co-authored-by: carrysauce <carrysauce@users.noreply.github.com>
Co-authored-by: Daniel A. Wozniak <daniel.wozniak@broadcom.com>
saltstack#68784)

* fix(pip): normalize names in list_freeze_parse and detect modern pip satisfied message

* ref: change string formatting

* enh: add PR suggestions
…#69712)

Since 3008, pillar.get masks scalar string values by default, so
modules that read pillar and use the value operationally were receiving
the redaction placeholder instead of the real data. Pass unmask=True at
each genuine consumer call site, matching the file.managed
contents_pillar path and the x509_v2 _get_signing_policy fix (saltstack#69636):

- gpg: signing/encryption passphrase (create_key, delete_key,
  export_key, sign, encrypt, decrypt)
- debuild_pkgbuild / rpmbuild_pkgbuild: signing passphrase and public
  and private key names
- x509 / ssh_pki: signing policies
- tls: certificate extension definitions
- oracle: database connection data (show_dbs consumed by run_query)
- pyobjects: the Map renderer merge pillar

Display-only reads (oracle show_dbs no-arg, show_pillar) are left masked
by design. The pyobjects renderer pillar()/SaltObject shortcuts have the
same masking exposure and are left for a separate render-context fix.

Refs saltstack#69711
saltstack#69714)

Dependabot bumps the shared floor for these packages to a version that
no longer supports Python 3.9 (jaraco.functools 4.5.0, jaraco.context
6.1.2, msgpack 1.2.1, opentelemetry 1.43.0 / exporter-prometheus 0.64b0,
pyopenssl 26.3.0, virtualenv 21.5.1, xmldiff 3.0, xxhash 3.8.0, zipp
4.1.0). With a single unmarked floor the py3.9 lock targets become
unresolvable and every grouped pip-updates PR fails pre-commit/lint/build.

Split each into a python_version < '3.10' branch capped at the last
3.9-compatible release plus an open py>=3.10 branch, mirroring the
existing cryptography/aiohttp/urllib3 splits. Dependabot then only bumps
the uncapped py>=3.10 line and leaves 3.9 resolvable. No lock changes are
needed now (uv already backtracks py3.9 to these versions); this only
future-proofs the input floors.
saltstack#69715)

Dependabot bumps the shared floor for these packages to a version that
no longer supports Python 3.9 (jaraco.functools 4.5.0, jaraco.context
6.1.2, msgpack 1.2.1, opentelemetry 1.43.0 / exporter-prometheus 0.64b0,
pyopenssl 26.3.0, virtualenv 21.5.1, xmldiff 3.0, xxhash 3.8.0, zipp
4.1.0). With a single unmarked floor the py3.9 lock targets become
unresolvable and every grouped pip-updates PR fails pre-commit/lint/build.

Split each into a python_version < '3.10' branch capped at the last
3.9-compatible release plus an open py>=3.10 branch, mirroring the
existing cryptography/aiohttp/urllib3 splits. Dependabot then only bumps
the uncapped py>=3.10 line and leaves 3.9 resolvable. No lock changes are
needed now (uv already backtracks py3.9 to these versions); this only
future-proofs the input floors.
dwoz and others added 18 commits August 26, 2026 04:38
…#70090)

Round-2 verification of PR saltstack#70145 found that the original scenario
test in tests/pytests/scenarios/cluster/test_haproxy_isolated_fs.py
did not distinguish the fix from the pre-fix code: with the default
``localfs_key`` driver the master-keys cache and the on-disk PEM are
the same file, so the cache-refresh in the fix is a tautology and the
assertion passes on both sides.  Attempting to force ``mmap_key`` in
the fixture surfaced a separate 2-master isolated-cluster bring-up
bug (peer keys never propagate under mmap_key discover, "Peer key
missing" loops), which blocks scenario coverage of the reporter's
exact driver until that separate issue is resolved.

To actually anchor the fix add a focused unit test in
tests/pytests/unit/channel/test_server.py that drives
``MasterPubServerChannel.handle_pool_publish`` with a synthesized
``cluster/peer/join-reply`` payload and asserts:

- ``master_key.cache.store("master_keys", "cluster.pem", <wire>)`` is
  called (regression: pre-fix ``store()`` is never invoked, cache
  serves the joiner's placeholder to auth replies under mmap_key).
- ``master_key.cache.store("master_keys", "cluster.pub", <wire>)`` is
  called with the wire pub bytes.
- ``master_key.cluster_key`` and ``master_key.key`` are rebound from
  the wire-delivered PEM so the running process signs cluster events
  with the shared identity immediately (no restart needed).

The unit test passes with the fix and fails with a clear
``Fix regression: join-reply handler must write cluster.pem through
master_key.cache.store...`` message when the fix's +40-line block in
salt/channel/server.py is reverted -- verified locally by
git-checkout of the parent commit.

The scenario test is retained as a smoke test for the wire-delivery
path (``test_isolated_cluster_pem_propagates`` covers the same
invariant more thoroughly) with a docstring calling out its
tautology under ``localfs_key`` and the mmap_key follow-up work.
…-08-26-26

# Conflicts:
#	.github/workflows/ci.yml
#	.github/workflows/depcheck.yml
#	.github/workflows/nightly-stress-test.yml
#	.github/workflows/nightly.yml
#	.github/workflows/scheduled.yml
#	.github/workflows/staging.yml
#	.github/workflows/templates/layout.yml.jinja
#	.pre-commit-config.yaml
#	doc/topics/development/conventions/style.rst
#	noxfile.py
#	requirements/base.txt
#	requirements/constraints.txt
#	requirements/static/ci/common.txt
#	requirements/static/ci/py3.10/cloud.lock
#	requirements/static/ci/py3.10/darwin.lock
#	requirements/static/ci/py3.10/docs.lock
#	requirements/static/ci/py3.10/freebsd.lock
#	requirements/static/ci/py3.10/lint.lock
#	requirements/static/ci/py3.10/linux.lock
#	requirements/static/ci/py3.10/windows.lock
#	requirements/static/ci/py3.11/cloud.lock
#	requirements/static/ci/py3.11/darwin.lock
#	requirements/static/ci/py3.11/docs.lock
#	requirements/static/ci/py3.11/freebsd.lock
#	requirements/static/ci/py3.11/lint.lock
#	requirements/static/ci/py3.11/linux.lock
#	requirements/static/ci/py3.11/windows.lock
#	requirements/static/ci/py3.12/cloud.lock
#	requirements/static/ci/py3.12/darwin.lock
#	requirements/static/ci/py3.12/docs.lock
#	requirements/static/ci/py3.12/freebsd.lock
#	requirements/static/ci/py3.12/lint.lock
#	requirements/static/ci/py3.12/linux.lock
#	requirements/static/ci/py3.12/windows.lock
#	requirements/static/ci/py3.13/cloud.lock
#	requirements/static/ci/py3.13/darwin.lock
#	requirements/static/ci/py3.13/docs.lock
#	requirements/static/ci/py3.13/freebsd.lock
#	requirements/static/ci/py3.13/lint.lock
#	requirements/static/ci/py3.13/linux.lock
#	requirements/static/ci/py3.13/windows.lock
#	requirements/static/ci/py3.14/cloud.lock
#	requirements/static/ci/py3.14/darwin.lock
#	requirements/static/ci/py3.14/docs.lock
#	requirements/static/ci/py3.14/freebsd.lock
#	requirements/static/ci/py3.14/lint.lock
#	requirements/static/ci/py3.14/linux.lock
#	requirements/static/ci/py3.14/windows.lock
#	requirements/static/ci/py3.9/freebsd.lock
#	requirements/static/ci/py3.9/lint.lock
#	requirements/static/pkg/freebsd.txt
#	requirements/static/pkg/linux.txt
#	requirements/static/pkg/py3.10/darwin.lock
#	requirements/static/pkg/py3.10/freebsd.lock
#	requirements/static/pkg/py3.10/linux.lock
#	requirements/static/pkg/py3.10/windows.lock
#	requirements/static/pkg/py3.11/darwin.lock
#	requirements/static/pkg/py3.11/freebsd.lock
#	requirements/static/pkg/py3.11/linux.lock
#	requirements/static/pkg/py3.11/windows.lock
#	requirements/static/pkg/py3.12/darwin.lock
#	requirements/static/pkg/py3.12/freebsd.lock
#	requirements/static/pkg/py3.12/linux.lock
#	requirements/static/pkg/py3.12/windows.lock
#	requirements/static/pkg/py3.13/darwin.lock
#	requirements/static/pkg/py3.13/freebsd.lock
#	requirements/static/pkg/py3.13/linux.lock
#	requirements/static/pkg/py3.13/windows.lock
#	requirements/static/pkg/py3.14/darwin.lock
#	requirements/static/pkg/py3.14/freebsd.lock
#	requirements/static/pkg/py3.14/linux.lock
#	requirements/static/pkg/py3.14/windows.lock
#	requirements/static/pkg/py3.9/freebsd.lock
#	salt/modules/tls.py
#	tools/pkg/build.py
…-08-25-26

# Conflicts:
#	changelog/69986.fixed.md
The merge-forward from 3006.x kept the reverted `keybase.io/codecovsecurity`
signing-key URL (HTTP 404), which fails gpg --import under bash -e. Point
the Codecov Uploader Verification Key fetch at
`https://uploader.codecov.io/verification.gpg` (same fingerprint,
`27034E7FDB850E0BBC2C62FF806BB28AED779869`), matching 3006.x/master.
…solated-haproxy

[3008.x] Fix cluster isolated-filesystem auth failure behind HAProxy (saltstack#70090)
…-08-25-26

# Conflicts:
#	.github/workflows/ci.yml
#	.github/workflows/depcheck.yml
#	.github/workflows/nightly.yml
#	.github/workflows/scheduled.yml
#	.github/workflows/staging.yml
#	.github/workflows/templates/ci.yml.jinja
#	.pre-commit-config.yaml
#	cicd/shared-gh-workflows-context.yml
#	requirements/constraints.txt
#	requirements/static/ci/py3.10/freebsd.lock
#	requirements/static/ci/py3.10/lint.lock
#	requirements/static/ci/py3.10/linux.lock
#	requirements/static/ci/py3.10/windows.lock
#	requirements/static/ci/py3.11/cloud.lock
#	requirements/static/ci/py3.11/darwin.lock
#	requirements/static/ci/py3.11/freebsd.lock
#	requirements/static/ci/py3.11/lint.lock
#	requirements/static/ci/py3.11/linux.lock
#	requirements/static/ci/py3.11/windows.lock
#	requirements/static/ci/py3.12/freebsd.lock
#	requirements/static/ci/py3.12/lint.lock
#	requirements/static/ci/py3.12/windows.lock
#	requirements/static/ci/py3.13/freebsd.lock
#	requirements/static/ci/py3.13/lint.lock
#	requirements/static/ci/py3.13/windows.lock
#	requirements/static/ci/py3.14/cloud.lock
#	requirements/static/ci/py3.14/darwin.lock
#	requirements/static/ci/py3.14/docs.lock
#	requirements/static/ci/py3.14/freebsd.lock
#	requirements/static/ci/py3.14/lint.lock
#	requirements/static/ci/py3.14/linux.lock
#	requirements/static/ci/py3.14/windows.lock
#	requirements/static/ci/py3.9/freebsd.lock
#	requirements/static/ci/py3.9/lint.lock
#	requirements/static/pkg/py3.10/freebsd.lock
#	requirements/static/pkg/py3.10/windows.lock
#	requirements/static/pkg/py3.11/freebsd.lock
#	requirements/static/pkg/py3.11/windows.lock
#	requirements/static/pkg/py3.12/darwin.lock
#	requirements/static/pkg/py3.12/freebsd.lock
#	requirements/static/pkg/py3.12/linux.lock
#	requirements/static/pkg/py3.12/windows.lock
#	requirements/static/pkg/py3.13/darwin.lock
#	requirements/static/pkg/py3.13/freebsd.lock
#	requirements/static/pkg/py3.13/linux.lock
#	requirements/static/pkg/py3.13/windows.lock
#	requirements/static/pkg/py3.14/darwin.lock
#	requirements/static/pkg/py3.14/freebsd.lock
#	requirements/static/pkg/py3.14/linux.lock
#	requirements/static/pkg/py3.14/windows.lock
#	requirements/static/pkg/py3.9/freebsd.lock
#	salt/channel/server.py
#	salt/modules/tls.py
#	tests/integration/modules/test_tls.py
#	tests/pytests/functional/utils/test_process.py
#	tests/pytests/unit/channel/test_server.py
#	tools/pkg/build.py
- Bump python_version 3.14.6 -> 3.14.7 to match relenv 0.22.25 assets
  (relenv 0.22.25 ships 3.14.7, not 3.14.6, causing 'HTTP Error 404:
  Not Found' in Build Salt Onedir for every platform).
- Regenerate 8 freebsd lock files via pre-commit to consolidate
  clr-loader/pythonnet conditionals that pip-compile wanted to
  simplify (python_full_version < '3.13' -> just sys_platform ==
  'win32' since the Windows onedir line uses 3.13+ everywhere except
  py3.9's 3.10 boundary).
relenv 0.22.25 ships Python 3.14.7 whose sysconfig no longer sets
-undefined dynamic_lookup in LDSHARED. Source-built extensions on
macOS (e.g. timelib) then fail to link because they reference
private CPython symbols like _PyBaseObject_Type.
…ite_buffer defaults

Four xfail(strict=True) tests for four backpressure findings against
salt.transport.tcp on 3008.x head, all cross-corroborated by two
independent fuzz runs on 2026-08-26:

* test_pub_server_stream_read_unpacker_has_max_buffer_size_cap (P1) --
  PubServer._stream_read constructs salt.utils.msgpack.Unpacker() with
  no max_buffer_size, pinning ~1 MB C-heap per subscriber on
  msgpack 1.2.1.

* test_master_default_ipc_write_buffer_is_bounded (P3 / R2-2026-08) --
  the ipc_write_buffer opt exists on 3008.x but defaults to 0
  (unbounded), so slow subscribers grow the per-stream write buffer to
  ~47 MB before publish_drain_timeout fires.

* test_pub_server_publish_payload_caps_in_flight_drain_tasks (R1-2026-08 /
  N2) -- publish_payload schedules one asyncio.ensure_future() per
  subscriber per event with no cap; a 20k-event burst against 8
  subscribers produced 160k pending drain tasks and drove RSS to 820 MB
  (100k burst hit 2.9 GB and starved the io_loop). Asserts <= 64
  outstanding tasks after 1000 publishes to one subscriber.

* test_discard_slow_client_cancels_pending_drain_tasks (N1) --
  _discard_slow_client does not cancel in-flight drain tasks for the
  discarded subscriber; their closures pin the payload bytes and client
  reference for up to publish_drain_timeout seconds.

The R1-2026-08 / N1 fixes will remove the xfail markers on the last two
tests in the next commit. P1 and P3 fixes are separate work items and
their xfail markers stay for the corresponding future PRs.
Under a bursty producer the pre-fix path scheduled one
``asyncio.ensure_future(_make_drain_task(client)(fut))`` per
(subscriber x event) with no cap.  A 20k-event burst against 8
subscribers produced 160k pending drain tasks (RSS 820 MB); 100k
events reached 2.9 GB RSS and starved the io_loop hard enough that
consumer subscribers stopped receiving mid-burst.  Each drain task
pinned an asyncio Task + Future + coroutine + Context + Handle +
TimerHandle transitively.

Give each Subscriber a per-subscriber drain coroutine reading write
futures from a bounded ``asyncio.Queue`` (default 500, configurable
via the new ``pub_server_write_queue_size`` master opt).
``publish_payload`` still calls ``stream.write(payload)`` inline (so
byte ordering and side effects match the pre-fix semantics), then
enqueues the returned future for the drainer to ``asyncio.wait_for``.
In-flight drain tasks are now capped at one per subscriber regardless
of burst size.  When the queue fills, the subscriber is treated as
slow and disconnected -- the same fast-fail path
``StreamBufferFullError`` already takes.

Also fix N1: ``_discard_slow_client`` now cancels the drainer task
and drops its queue entry, so the payload futures captured by the
drainer are released immediately rather than pinned until each queued
``wait_for`` hits its ``publish_drain_timeout`` (up to 5s per pending
future -- 46 MB retained-bytes measured in the fuzz report).

Removes the ``xfail`` markers on
``test_pub_server_publish_payload_caps_in_flight_drain_tasks`` and
``test_discard_slow_client_cancels_pending_drain_tasks`` in
``tests/pytests/unit/transport/test_tcp_pubserver_backpressure.py``;
the P1 and P3 xfails there stay (separate fixes).

Fixes saltstack#70147
The 500-slot per-subscriber drain queue introduced in the prior
commit was too tight for realistic event bursts.  Under the CI
stress rig, a producer that fires 5000 events in one broadcast pushes
publish_payload through 5000 sequential enqueues before the drainer
coroutine gets a scheduler slot; the writer trips QueueFull well
before the drainer has a chance to make forward progress, and the
subscriber gets a false-positive "slow" discard.  The failure showed
up as ConnectionResetError: EP pub socket closed on
test_throughput_floor_single_subscriber and
test_multi_subscriber_fanout across every Linux distro's integration
split-7 job (zeromq and tcp), reproducible locally in a single-second
run of the same stress test.

Two changes:

1. Raise ``_DEFAULT_WRITE_QUEUE_MAXSIZE`` from 500 to 10000 (and
   mirror in ``DEFAULT_MASTER_OPTS[pub_server_write_queue_size]``).
   Each queue slot holds one tornado Future (a few hundred bytes),
   so 10k slots x 200 subscribers is ~2 MB retained -- vs. the
   pre-fix path's 820 MB / 2.9 GB in fire-and-forget asyncio Tasks
   (issue saltstack#70147).  Bursts of a few thousand events fit without
   backpressure; genuinely runaway bursts still fail-fast.

2. Preserve the QueueFull -> ``_discard_slow_client`` fast-fail path
   for genuinely wedged subscribers, but the docstring on
   ``_submit_write`` now spells out why we deliberately do NOT block
   the writer on ``queue.put`` when the queue fills: awaiting here
   propagates backpressure through the entire publish_payload loop
   and stalls broadcast to every other subscriber, and the drainer's
   own head-of-line ``publish_drain_timeout`` watchdog already
   detects a truly stuck peer.

Local reproduction: with the 500-cap the stress test dies at fut 279
(280 messages received); with the 10k cap all 5000 messages come
through in <200 ms.
…-08-25-26

Resolve salt/config/__init__.py conflict: keep both the 3007.x fix
(publish_drain_timeout=60.0, bd8ff52) and the 3008.x addition
(pub_server_write_queue_size=10000, b8f2622).

Also fix a Class-3 silent regression in salt/transport/tcp.py:
3008.x b8f2622 rewrote publish_payload's inline drain logic
into a new _drain_loop method whose fallback was still 5.0, which
would silently revert 3007.x commit bd8ff52 that bumped the
fallback to 60.0.  Restore 60.0 in _drain_loop.
The merge silently kept 3007.x's copy of every .lock under
requirements/static/{ci,pkg}/py3.*/ , stranding the environment
on stale pins.  Most visibly, redis was left at 3.5.3 while
3008.x had already moved to 7.4.0 -- redis-py 3.5 does not
accept the bylex kwarg on Redis.zrange(), so every subtest in
tests/pytests/functional/cache/test_redis.py::test_caching
tripped 'Redis.zrange() got an unexpected keyword argument
bylex' from salt/cache/redis_cache.py:344.  Reproduced locally
with pip install redis==3.5.3 (missing bylex) vs 7.4.0 (present).

Fix: restore every drifted .lock to origin/3008.x, then rerun
the pre-commit pip-compile hooks so the merged 3007.x floors
(attrs>=26.1.0 / charset-normalizer>=3.4.7 for CVE) are honoured
in the regenerated locks.
The previous lock resync started from origin/3008.x's lock files,
which resolved SRP-controlled packages to older versions than
3007.x had shipped.  pip-compile then kept them because the
existing floors were satisfied -- so cryptography, pyopenssl,
msgpack, requests, gitpython, and pyasn1 all silently regressed
below the 3007.x SRP baseline across every python x platform
combination.

Bump the base.txt floors to the 3007.x-resolved versions and
re-run the pip-compile hooks:

  cryptography  >=48.0.0 -> >=50.0.0   (py>=3.10)
  gitpython     >=3.1.50 -> >=3.1.59
  msgpack       >=1.1.2  -> >=1.2.1    (py>=3.10; drop the >=3.13 sub-floor,
                                        1.2.1 works on 3.13 -- 3007.x runs it)
  pyasn1        >=0.6.3  -> >=0.6.4
  pyopenssl     >=26.2.0 -> >=26.4.0   (py>=3.10)
  requests      >=2.32.5 -> >=2.34.2   (py>=3.11)

Locks now resolve to cryptography 50.0.1 / gitpython 3.1.60 /
msgpack 1.2.1 / pyasn1 0.6.4 / pyopenssl 26.4.0 / requests 2.34.2
across every py3.10+ target -- meets or exceeds the 3007.x
baseline everywhere.
The 3006.x SRP PR saltstack#70130 raised setuptools to >=82.0.1 in
constraints.txt; our merge inherited only the 3007.x >=78.1.1
floor and pip-compile resolved to setuptools==82.0.0 across
every pkg lock (regression from 3007.x's own 84.0.0 baseline).

Bump the constraint to 82.0.1 to match the SRP intent. Locks
now resolve to setuptools==84.0.0 (>= 3007.x) across every
py x platform.
Two targeted changes so nightly builds sign RPMs when signing secrets
are present on the running repo, without hardcoding a specific key.

templates/build-packages.yml.jinja:
  Flip the nightly-environment override from
    sign-rpm-packages: false
  to
    sign-rpm-packages: true
  and regenerate nightly.yml. Nightly builds now opt in to signing.

build-packages.yml (not generated):
  In the Setup GnuPG step, after `gpg --import`, discover the
  fingerprint of the just-imported key via
    gpg --list-secret-keys --with-colons | awk -F: '$1=="fpr" ...'
  and export SIGN_KEY_ID to $GITHUB_ENV. In the Build RPM step,
  replace the hardcoded --key-id=64CBBC8173D76B3F with
  --key-id=${env.SIGN_KEY_ID}. rpmsign now uses whatever key
  material SIGNING_GPG_KEY contained, regardless of fingerprint.

Combined effect:
  - saltstack/salt-nightlies with a dedicated nightly signing secret
    set: signs with that key. Blast-radius isolated from the stable
    SaltProjectKey held on saltstack/salt.
  - Any repo without SIGNING_GPG_KEY set: Setup GnuPG is gated by
    `inputs.sign-rpm-packages` so it doesn't run, SIGN_KEY_ID stays
    unset, --key-id is not appended, no signing attempted, no failure.
  - saltstack/salt stable release path (staging.yml passes sign-rpm-
    packages: true and has SIGNING_GPG_KEY set to the SaltProjectKey):
    imports as before, discovers 64CBBC8173D76B3F from the keyring,
    signs identically. Behavior unchanged.

No caller-side plumbing; no vars; no per-repo workflow config.
@CrazyHackGUT
CrazyHackGUT requested a review from a team as a code owner August 27, 2026 16:24
@welcome

welcome Bot commented Aug 27, 2026

Copy link
Copy Markdown

Hi there! Welcome to the Salt Community! Thank you for making your first contribution. We have a lengthy process for issues and PRs. Someone from the Core Team will follow up as soon as possible. In the meantime, here's some information that may help as you continue your Salt journey.
Please be sure to review our Code of Conduct. Also, check out some of our community resources including:

There are lots of ways to get involved in our community. Every month, there are around a dozen opportunities to meet with other contributors and the Salt Core team and collaborate in real time. The best way to keep track is by subscribing to the Salt Community Events Calendar.
If you have additional questions, email us at saltproject.pdl@broadcom.com. We're glad you've joined our community and look forward to doing awesome things with you!

@CrazyHackGUT
CrazyHackGUT force-pushed the fix/mem-leak-webhook-engine branch from 1ff629f to e25b24e Compare August 27, 2026 16:26
dwoz and others added 6 commits August 27, 2026 10:19
…008.x

[3008.x] nightly: sign RPM packages with whichever key is in the signing secret
saltstack#70141 enabled RPM signing for nightly builds and pointed the workflow
at the nightly environment's SIGNING_GPG_KEY/SIGNING_PASSPHRASE secrets,
but the shared template that generates the build-pkgs-onedir /
build-pkgs-src callers only sets `secrets: inherit` for the staging
environment:

    <% if gh_environment == 'staging' -%>
    secrets: inherit
    <% endif -%>

Without `secrets: inherit` (or an explicit `secrets:` block), reusable
workflows cannot see caller secrets, so build-packages.yml's Setup
GnuPG step receives empty SIGNING_GPG_KEY / SIGNING_PASSPHRASE.
`echo "" | gpg --import -` fails with:

    gpg: no valid OpenPGP data found.
    gpg: Total number processed: 0
    Process completed with exit code 2.

Observed in saltstack/salt-nightlies run 33098010828 (3008.x nightly
first run with signing enabled). The Setup GnuPG step's env log
showed:

    SIGNING_GPG_KEY:
    SIGNING_PASSPHRASE:

(literal blanks, not `***` redaction) — proving the secrets weren't
resolved, not that they were empty.

Fix: widen the conditional to `gh_environment != 'ci'`, matching the
pattern of `environment:` / `sign-macos-packages:` / `sign-rpm-packages:`
lines just below in the same template. Every non-CI caller now inherits
secrets. Regenerates nightly.yml with `secrets: inherit` on both
build-pkgs-onedir and build-pkgs-src callers.

Local pre-commit: Generate GitHub Workflow Templates Passed, Lint
GitHub Actions Workflows Passed.
…008.x

[3008.x] templates/build-packages: pass secrets: inherit for nightly too
The `SaltVersionsInfo` table in `salt/version.py` had CHLORINE (3007)
marked `released=True` but ARGON (3008) not, even though 3008.0,
3008.1, and 3008.2 have all been tagged and shipped.

`SaltVersionsInfo.current_release()` iterates the version list and
returns the last codename with `released=True`. With ARGON missing
the flag it returns CHLORINE, which then gets used as the baseline
in `__discover_version()`. On the 3008.x branch that produces version
strings like `3007.14+2643.gca4940b8f0` for nightly builds -- packages
end up named `salt-3007.14+N-0.x86_64.rpm` instead of the expected
`salt-3008.2+N-0.x86_64.rpm`.

Regression window on 3008.x: introduced by the 2026-08-25 merge from
3007.x (commit 17c1f44 "Merge remote-tracking branch
'origin/3007.x' into merge/3007.x/3008.x-08-25-26"). Prior nightlies
were correctly labeled -- e.g. build 210 from 2026-08-19 shipped as
`salt-3008.2+210.g9a41326f33`. Master has the same bug and was
similarly affected (though master nightlies have been failing for
other reasons and nobody noticed).

Fix is a one-line flip:

    ARGON = SaltVersion("Argon", info=3008, released=True)

Needs to be backported to 3008.x. Any active supported release
branch whose `current_release()` should return ARGON needs the same.

Discovered while verifying nightly RPM signing on a live 3008.x
build (salt-nightlies run 33103981309): "Prepare Release" step
printed "3007.14+2643.gca4940b8f0" from a 3008.x checkout.
…008.x

[3008.x] version: mark ARGON (3008) as released
@CrazyHackGUT
CrazyHackGUT force-pushed the fix/mem-leak-webhook-engine branch from e25b24e to 5ee30c5 Compare August 27, 2026 20:47
@CrazyHackGUT

Copy link
Copy Markdown
Author

I recreate for merging to 3008.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.