Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
228 lines (214 loc) · 12.1 KB
/
Copy pathpyproject.toml
File metadata and controls
228 lines (214 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
[project]
# The PyPI distribution is "configmanbearpig" — this collector IS ConfigManBearPig 2.0,
# the Python successor to ConfigManBearPig.ps1. Three names are in play, deliberately:
# distribution configmanbearpig what users install (uv tool install openhound --with configmanbearpig)
# import pkg openhound_sccm what the code imports
# CLI verb sccm what users type (openhound collect sccm)
# The verb names the data source, matching every other OpenHound collector (jamf, okta, github).
name = "configmanbearpig"
# Version comes from the git tag via hatch-vcs (see [tool.hatch.version] below).
dynamic = ["version"]
description = "ConfigManBearPig 2.0.2 — OpenHound/BloodHound OpenGraph collector for Microsoft Configuration Manager (SCCM/MECM)"
readme = "README.md"
requires-python = "<3.15,>=3.13"
authors = [
{name = "Chris Thompson (@_Mayyhem)", email = "cthompson@specterops.io"}
]
license = "Apache-2.0"
license-files = ["LICENSE"]
keywords = ["sccm", "mecm", "configmgr", "bloodhound", "opengraph", "openhound", "attack-paths"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Information Technology",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Security",
]
# Declaring `openhound` diverges from openhound-jamf, which declares no dependencies at
# all — deliberately: that collector uses only the public decorator surface, whereas this
# one imports a dozen openhound modules, hand-registers commands on its Typer objects, and
# assigns `app.converter`. Without the declaration, installing this package yields
# something that cannot import.
dependencies = [
# The OpenHound framework. The floor is the version this collector is validated
# against; there is no upper cap, because an invented bound guarantees a re-release
# the day 0.3.0 ships. If a future release breaks the hand-registration, a version
# check in main.py with a clear message is more honest than a guessed resolver bound.
"openhound>=0.2.12",
# Shared pure-Python collector infrastructure (TDS/NTLM/Kerberos/EPA, LDAP/AD,
# WMI, DNS discovery, duckdb-safe, DLT source bridge, logging, graph base).
# Carries impacket/ldap3/dnspython/pywin32/winkerberos/cryptography transitively.
# Published from https://github.com/Mayyhem/openhound-collector-common.
#
# Capped below 0.2 because a 0.x library makes no API-stability promise — an
# uncapped floor would let a breaking 0.2 land silently in users' installs.
# Floor is 0.1.4, which fixes two things every operator sees: it silences
# impacket's Python 3.14 SyntaxWarning at the import that triggers it
# (clients/mssql.py -> impacket.tds -> impacket.mssql.version:182, a return
# inside a finally block), and it shortens a skipped transform from a five-line
# DuckDB exception dump to one line. Both are console noise this collector
# cannot suppress from its own side. 0.1.3 added StagePaths.graph_zip, which the
# --run-all output summary reads to name the archive the chain actually wrote.
"openhound-collector-common>=0.1.4,<0.2.0",
# LDAP client used by the LDAP collection phase (clients/ad.py).
# 2.10.2rc4 is the first ldap3 release that exports ENCRYPT and
# TLS_CHANNEL_BINDING constants, required for NTLM sign-and-seal and
# LDAP channel binding tokens against signing-required DCs.
"ldap3>=2.10.2rc4",
# Windows-only Kerberos backend for ldap3 SASL GSSAPI. Drives the
# "no explicit creds → bind with the current user's TGT via SSPI"
# path in clients/ad.py. Wheels exist for CPython 3.13/3.14 on Windows
# x64; on Linux/macOS this package is ignored (and ldap3 falls back to
# the `gssapi` package, which we don't bundle — Linux collectors that
# need integrated auth should `pip install gssapi[kerberos]`).
"winkerberos>=0.10.0; sys_platform == 'win32'",
# Windows SSPI bindings used for current-user NTLM fallback when no
# explicit password is provided and Kerberos cannot build an LDAP SPN
# (for example --dc is an IP address with no reverse DNS).
"pywin32>=306; sys_platform == 'win32'",
# DNS SRV / ADIDNS lookups for the DNS phase.
"dnspython>=2.4.0",
# Used by impacket-based per-host phases.
"impacket>=0.11.0",
# HTTP transport for the AdminService REST API and the HTTP role-probe
# collector (clients/http.py). Auth tokens are minted by impacket / pywin32
# SSPI and carried in the Authorization header; requests only moves bytes.
"requests>=2.31.0",
# X.509 parsing for the unauthenticated HTTP collector's sitesigncert probe
# (collectors/http.py): the management point's site-signing certificate is
# parsed to read its issuer CN ("Site Server") and SAN DNS name, identifying
# the site server. No stdlib X.509 parser exists; cryptography is the standard.
"cryptography>=42.0.0",
]
# NOTE: there is deliberately no [tool.uv.sources] table here.
#
# A `path = "../openhound-collector-common"` entry redirecting the shared library to a
# sibling checkout is convenient locally and breaks every environment that does not have
# one. CI is the obvious such environment -- the runner checks out this repository alone --
# and `uv sync` fails there with:
#
# error: Failed to generate package metadata for
# `openhound-collector-common @ editable+../openhound-collector-common`
# Caused by: Distribution not found at: file:///.../openhound-collector-common
#
# so does a clone by anyone who only wants this repo. The library is on PyPI, so the
# capped range in [project.dependencies] resolves on its own and needs no redirect.
#
# To develop against a live library checkout, clone it as a SIBLING of this repo and add
# the table back locally -- then keep git from ever staging it:
#
# [tool.uv.sources]
# openhound-collector-common = { path = "../openhound-collector-common", editable = true }
#
# git update-index --skip-worktree pyproject.toml # so `git add -A` cannot pick it up
# uv sync --group dev
#
# To change this file legitimately: `git update-index --no-skip-worktree pyproject.toml`,
# edit, commit, then re-apply the skip.
#
# Two things that do NOT work, both verified: `uv pip install -e ../openhound-collector-common`
# installs a COPY, not a live editable -- edits to the library are invisible, and the next
# `uv sync` reverts it to the PyPI version anyway. And `sources` in a `uv.toml` is rejected
# outright ("only applicable in the context of a project"), besides shadowing [tool.uv] above.
#
# You only need any of this when editing the LIBRARY. For collector-only work, a plain
# `uv sync --group dev` against the published library is the simpler and safer default.
[tool.uv]
# Never use a uv-managed (python-build-standalone) interpreter: on Windows its
# libcrypto-3-x64.dll lacks the OPENSSL_Applink cross-CRT shim, so any TLS handshake
# aborts the process — which is every AdminService, LDAPS, and MSSQL code path.
#
# The constraint this imposes is NOT Windows-only, and describing it as "harmless on
# Linux" is what hid a CI failure: uv may not download an interpreter anywhere, so every
# environment must already have one satisfying requires-python (>=3.13). That is why
# .github/workflows/*.yml provision Python explicitly — ubuntu-latest's system Python is
# 3.12 and uv fails outright rather than falling back.
python-preference = "only-system"
[build-system]
requires = ['hatchling', 'hatch-vcs']
build-backend = 'hatchling.build'
[project.urls]
Homepage = "https://github.com/SpecterOps/ConfigManBearPig"
Repository = "https://github.com/SpecterOps/ConfigManBearPig"
Issues = "https://github.com/SpecterOps/ConfigManBearPig/issues"
# Only this directory ships in the wheel. Any data file the collector reads at runtime
# (extension.yaml, schema_SCCM.json, schema_MSSQL.json) MUST live inside it — a path
# resolved above the package works in a checkout and vanishes once installed.
[tool.hatch.build.targets.wheel]
packages = ["src/openhound_sccm"]
[tool.hatch.version]
source = "vcs"
[tool.hatch.version.raw-options]
# PyPI rejects versions with a local segment (+g1a2b3c), which setuptools-scm adds by
# default on any non-tag commit.
local_scheme = "no-local-version"
# GitHub's source archives have no .git directory. Git expands .git_archival.txt
# there; the archive directory name is a fallback if the host omits that expansion.
# With neither source available, fail rather than silently stamp 0.0.0.
parentdir_prefix_version = "ConfigManBearPig-"
# Only consider MAJOR.MINOR.PATCH tags when deriving the version.
#
# This repository also carries `v1.2-powershell`, which archives the final PowerShell
# release. Without this filter setuptools-scm picks that tag up, fails to parse it, and
# raises -- so `uv lock`, `uv sync` and every build break outright.
git_describe_command = [
"git", "describe", "--dirty", "--tags", "--long", "--match", "v[0-9]*.[0-9]*.[0-9]*",
]
[dependency-groups]
# `openhound` is deliberately absent here: it is declared in [project.dependencies] as a
# version range, and a direct git reference would outrank that range during resolution —
# leaving local runs on an unpublished commit while users resolved the published version.
# That divergence is what let a packaging bug ship once already.
dev = [
"pre-commit>=4.5.1",
"pytest>=9.0.1",
# Capped below 0.16: that release widened ruff's DEFAULT rule selection well past
# E4/E7/E9/F. The shared library went from clean to 173 findings on that bump with no
# code change, and this package has the same exposure — a fresh `uv lock` would turn
# CI red. Adopting the wider set is a deliberate modernization pass, not a surprise.
"ruff>=0.15.5,<0.16",
"mypy>=1.19.1",
# Type stubs for the three untyped runtime deps that HAVE published stubs.
# (impacket, openhound and sspi have none — those get ignore_missing_imports
# overrides in [tool.mypy] instead.) Real signatures for ldap3 in particular
# are worth having: the LDAP phase is one of the most-used code paths here.
"types-ldap3>=2.9",
"types-pywin32>=306",
"types-pyasn1>=0.6",
]
[project.entry-points."openhound.sources"]
sccm = "openhound_sccm.main:app"
[tool.mypy]
enable_error_code = ["possibly-undefined"]
# pywin32 ships no type information, so importing win32com is otherwise flagged
# as an untyped import. It's used only in the Windows-only local collector.
[[tool.mypy.overrides]]
module = ["win32com.*"]
ignore_missing_imports = true
# None of these ship a py.typed marker, so mypy can't see their signatures and
# reports import-untyped for every module we import from them. That is a fact
# about those distributions, not something fixable here — and the alternative
# (writing stubs for the parts of impacket and the framework this collector
# touches) is a far larger surface than the collector itself.
# impacket — the auth/DCOM/TDS/Kerberos stack, imported across clients/
# openhound — the framework: Typer groups, assets, preproc/convert, destinations
# sspi — pywin32's SSPI bindings, current-user Windows auth (win32 only)
# Their call sites are exercised by the offline tests and the live lab run, which
# is where a signature mismatch actually surfaces.
[[tool.mypy.overrides]]
module = ["impacket.*", "openhound.*", "sspi"]
ignore_missing_imports = true
# The shared library ships no py.typed marker yet (adding one would make mypy
# type-check its API against both extensions' usage — a separate hygiene task with
# its own validation). Until then, treat its imports as untyped so SCCM's mypy
# doesn't accrue an import-untyped note for every shared module adopted per phase.
[[tool.mypy.overrides]]
module = ["openhound_collector_common.*"]
ignore_missing_imports = true
[tool.pytest.ini_options]
# Pinned to the *_test.py suffix only. pytest's default also accepts the test_*.py
# prefix; allowing both is what let the suite drift into two conventions at once.
# A consequence worth knowing: a file named test_something.py is now silently NOT
# collected by `pytest tests`, so a new test added under the old prefix would pass
# by never running. (Naming it explicitly on the command line still collects it.)
python_files = "*_test.py"