[Bug]: Deltaproxy sub-proxies share one module object, so module-level state in proxy/execution modules leaks across all controlled minions

Đang mở
#70,760 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 3 ngày

@ITJamie đang làm issue này rồi.

Từ ngày 10/11/2026.

  • #70766 của @ITJamie — đang mở

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
35/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
backend

Hướng nghiên cứu

Start by reading salt/loader/lazy.py, especially LazyLoader._load_module() and subproxy_post_master_init(), to see how loaded_base_name builds the module namespace and how sys.modules reuses module objects. Reproduce with the fakedev proxy/module from the report, comparing fakedev.host, fakedev.context_host and grains.item device_host across minion1-3. Done means each sub-proxy has isolated module globals and every controlled minion reports its own host.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

bug needs-triage
What happened?

Description

This is a follow-up to #68248. That issue was closed by #69426, which re-packs __grains__ into each sub-proxy's loaders. The underlying problem is broader and that change does not fix it: with deltaproxy, every sub-proxy's LazyLoader resolves a given module to the same Python module object. Any module-level state (connection handles, DETAILS dicts, caches) is therefore shared by all controlled minions, and the last sub-proxy to run init() wins. Every execution module that reaches the device through __proxy__ then returns the data of that one device, which is why grains, and in fact any function, return identical data for all controlled minions.

I applied #69426 to a 3006.9 install and the behaviour remains, as noted in #68248.

Root cause

In salt/loader/lazy.py, LazyLoader._load_module() loads modules with the deprecated spec.loader.load_module(), under a namespace built from loaded_base_name and the tag, e.g. salt.loaded.ext.proxy.fakedev. Every sub-proxy loader created in subproxy_post_master_init() uses the same loaded_base_name (salt.loaded), so they all compute the same namespace. When that name is already in sys.modules, load_module() re-executes the code into the existing module object instead of creating a new one.

The packed dunders (__context__, __grains__, __pillar__, __proxy__, ...) are injected as NamedLoaderContext objects that resolve through the active loader, so those stay per sub-proxy. Plain module globals do not.

Setup

Deltaproxy config (/etc/salt/proxy):

master: 127.0.0.1
metaproxy: deltaproxy
enable_fqdns_grains: False
multiprocessing: False   # also reproduces with True
skip_connect_on_init: True

Pillar top.sls:

base:
  dp_control:
    - control
  minion1:
    - p_minion1
  minion2:
    - p_minion2
  minion3:
    - p_minion3

control.sls:

proxy:
  proxytype: deltaproxy
  ids: [minion1, minion2, minion3]

p_minionN.sls (N = 1, 2, 3):

proxy:
  proxytype: fakedev
  host: 10.0.0.1N

salt://_proxy/fakedev.py. It follows the common pattern of keeping connection state in a module global, and also stores the host in __context__ for comparison:

__proxyenabled__ = ["fakedev"]
DETAILS = {}

def __virtual__():
    return "fakedev"

def init(opts):
    DETAILS["host"] = opts["proxy"]["host"]
    DETAILS["initialized"] = True
    __context__["fakedev"] = {"host": opts["proxy"]["host"]}

def initialized():
    return DETAILS.get("initialized", False)

def shutdown(opts):
    return True

def grains():
    return {"device_host": DETAILS.get("host")}

def host():
    return DETAILS.get("host")

def context_host():
    return __context__["fakedev"]["host"]

salt://_modules/fakedev.py:

__proxyenabled__ = ["fakedev"]

def __virtual__():
    return "fakedev"

def host():
    return __proxy__["fakedev.host"]()

def context_host():
    return __proxy__["fakedev.context_host"]()

Steps to Reproduce the behavior

salt-proxy --proxyid=dp_control -d
salt-key -A
salt 'minion*' fakedev.host --out=txt
salt 'minion*' fakedev.context_host --out=txt
salt 'minion*' grains.item device_host --out=txt
salt 'minion*' pillar.get proxy:host --out=txt

Output:

# module global: every minion reports the last-initialised device
minion1: 10.0.0.13
minion2: 10.0.0.13
minion3: 10.0.0.13

# __context__: correct per minion
minion1: 10.0.0.11
minion2: 10.0.0.12
minion3: 10.0.0.13

# grains derived from the proxy module: same wrong value everywhere
minion1: {'device_host': '10.0.0.13'}
minion2: {'device_host': '10.0.0.13'}
minion3: {'device_host': '10.0.0.13'}

# pillar: correct per minion
minion1: 10.0.0.11
minion2: 10.0.0.12
minion3: 10.0.0.13

The same result occurs with multiprocessing: True, because forked job processes inherit the already-shared module.

Expected behavior

Each sub-proxy should have isolated module state, so each controlled minion talks to and reports on its own device, as it would under a regular per-device salt-proxy.

Additional context

Related: #68248 (closed by #69426, which only addresses __grains__ re-packing).

Type of salt install

Official deb

Major version

3006.x

What supported OS are you seeing the problem on? Can select multiple. (If bug appears on an unsupported OS, please open a GitHub Discussion instead)

ubuntu-24.04, ubuntu-22.04

salt --versions-report output
Salt Version:
          Salt: 3006.9
 
Python Version:
        Python: 3.10.14 (main, Jun 26 2024, 11:44:37) [GCC 11.2.0]
 
Dependency Versions:
          cffi: 1.14.6
      cherrypy: unknown
  cryptography: 42.0.5
      dateutil: 2.8.1
     docker-py: Not Installed
         gitdb: Not Installed
     gitpython: Not Installed
        Jinja2: 3.1.4
       libgit2: Not Installed
  looseversion: 1.0.2
      M2Crypto: Not Installed
          Mako: Not Installed
       msgpack: 1.0.2
  msgpack-pure: Not Installed
  mysql-python: Not Installed
     packaging: 22.0
     pycparser: 2.21
      pycrypto: Not Installed
  pycryptodome: 3.19.1
        pygit2: Not Installed
  python-gnupg: 0.4.8
        PyYAML: 6.0.1
         PyZMQ: 23.2.0
        relenv: 0.17.0
         smmap: Not Installed
       timelib: 0.2.4
       Tornado: 4.5.3
           ZMQ: 4.3.4
 
System Versions:
          dist: ubuntu 22.04.4 jammy
        locale: utf-8
       machine: x86_64
       release: 5.15.0-185-generic
        system: Linux
       version: Ubuntu 22.04.4 jammy
Ngôn ngữ chính
Python
Star
15.8k
Fork
5.6k
Merge trung bình
5 ngày 9 giờ
Pull request đã merge (30 ngày)
99

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của saltstack/salt

Tất cả issue của saltstack/salt

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.