cmdbsyncer-enterprise 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmdbsyncer-enterprise
3
+ Version: 0.1.1
4
+ Summary: Enterprise features for cmdbsyncer (requires license)
5
+ Author-email: Kuhn & Ruess GmbH <info@kuhn-ruess.de>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: authlib>=1.3
@@ -0,0 +1,250 @@
1
+ # cmdbsyncer-enterprise
2
+
3
+ Commercial add-on for [cmdbsyncer](https://github.com/Kuhn-Ruess-GmbH/cmdbsyncer)
4
+ by Kuhn & Ruess GmbH. Adds licensed enterprise features to the open-source core
5
+ via a graceful import mechanism — the OSS stays fully functional without this
6
+ package.
7
+
8
+ ## Features
9
+
10
+ | Feature key | Description |
11
+ | ------------------ | -------------------------------------------------------- |
12
+ | `remote_user` | Web-server-provided `REMOTE_USER` single sign-on login |
13
+
14
+ Additional features are enabled by the license claims and registered at import
15
+ time. See *Adding a feature* below.
16
+
17
+ ## Architecture
18
+
19
+ ```text
20
+ +------------------+ +----------------------------+
21
+ | cmdbsyncer | | cmdbsyncer_enterprise |
22
+ | (OSS) | | (this package) |
23
+ | | | |
24
+ | application/ | | __init__.py |
25
+ | enterprise.py |<------+ load_and_verify() |
26
+ | run_hook(...) | | register_feature(...) |
27
+ +------------------+ +----------------------------+
28
+ ^
29
+ | reads
30
+ |
31
+ /etc/cmdbsyncer/license.jwt
32
+ (RS256-signed, verified against
33
+ embedded public_key.pem)
34
+ ```
35
+
36
+ - The OSS module `application/enterprise.py` imports `cmdbsyncer_enterprise`
37
+ inside a `try/except ImportError`. If the package is absent, missing a
38
+ license, or the license is invalid/expired, the import fails silently and
39
+ all enterprise hooks become no-ops.
40
+ - On a valid license, this package registers each feature listed in the
41
+ license claims against the OSS hook registry, binding it to its handler.
42
+ - OSS call sites use `run_hook('<feature_name>', ...)`; a missing hook
43
+ returns `None` and the OSS falls back to its default behaviour.
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.10+
48
+ - cmdbsyncer OSS installed in the same environment
49
+ - `authlib >= 1.3` (declared as a dependency)
50
+
51
+ ## Installation (customer)
52
+
53
+ ```sh
54
+ pip install cmdbsyncer-enterprise
55
+ ```
56
+
57
+ Place the license file provided by Kuhn & Ruess GmbH at:
58
+
59
+ ```text
60
+ /etc/cmdbsyncer/license.jwt
61
+ ```
62
+
63
+ Or set an alternate path via environment variable:
64
+
65
+ ```sh
66
+ export CMDBSYNCER_LICENSE=/opt/cmdbsyncer/license.jwt
67
+ ```
68
+
69
+ Restart cmdbsyncer. On successful activation you will see a log line like:
70
+
71
+ ```text
72
+ cmdbsyncer-enterprise active (license CUST-2026-001, customer Acme Corp,
73
+ expires 1778284800, features ['remote_user'])
74
+ ```
75
+
76
+ If the log stays silent and enterprise features are inactive, check the
77
+ application log for the `ImportError` message — it will name the exact
78
+ failure (missing file, bad signature, expired).
79
+
80
+ ## Configuration
81
+
82
+ ### `remote_user` feature
83
+
84
+ Enable it in cmdbsyncer's `local_config.py`:
85
+
86
+ ```python
87
+ REMOTE_USER_LOGIN = True
88
+ ```
89
+
90
+ Your reverse proxy (Apache / nginx / etc.) must authenticate the user and
91
+ set the `REMOTE_USER` WSGI environ variable. Make sure the proxy strips
92
+ any client-supplied value before forwarding the request — otherwise
93
+ anyone could impersonate any user by sending the header directly.
94
+
95
+ Users matched by `request.remote_user` are resolved against the `name`
96
+ field of the cmdbsyncer user model; disabled users are rejected.
97
+
98
+ ## Development setup (internal)
99
+
100
+ ```sh
101
+ git clone git@github.com:Kuhn-Ruess-GmbH/cmdbsyncer-enterprise.git
102
+ cd cmdbsyncer-enterprise
103
+
104
+ # Generate the RSA keypair once (keep private_key.pem OUT of the repo):
105
+ openssl genrsa -out private_key.pem 4096
106
+ openssl rsa -in private_key.pem -pubout \
107
+ -out src/cmdbsyncer_enterprise/public_key.pem
108
+
109
+ # Install the package editable into the cmdbsyncer venv for testing:
110
+ cd /path/to/cmdbsyncer
111
+ ./venv/bin/pip install -e /path/to/cmdbsyncer-enterprise
112
+ ```
113
+
114
+ `private_key.pem` and all `*.jwt` files are `.gitignore`-d. Store the
115
+ private key in the company password manager — it is the root of trust
116
+ for every customer license ever issued.
117
+
118
+ ## Issuing a license
119
+
120
+ ```sh
121
+ ./tools/issue_license.py \
122
+ --customer "Acme Corp" \
123
+ --license-id CUST-2026-001 \
124
+ --features remote_user \
125
+ --days 365 \
126
+ --private-key ./private_key.pem \
127
+ --out license.jwt
128
+ ```
129
+
130
+ Claims written into the JWT:
131
+
132
+ | Claim | Meaning |
133
+ | ------------ | ---------------------------------------- |
134
+ | `license_id` | Unique identifier for the license |
135
+ | `customer` | Customer name (free text) |
136
+ | `features` | List of feature keys to enable |
137
+ | `iat` | Issued-at (Unix timestamp, UTC) |
138
+ | `exp` | Expiry (Unix timestamp, UTC) — enforced |
139
+
140
+ Deliver `license.jwt` plus the matching wheel to the customer.
141
+
142
+ ## Building a release wheel
143
+
144
+ ```sh
145
+ make build
146
+ ```
147
+
148
+ The sdist and wheel land in `dist/`. The embedded `public_key.pem` is
149
+ shipped as package data (see `pyproject.toml`).
150
+
151
+ ## Publishing to PyPI
152
+
153
+ The package itself contains no secrets — only the RSA **public** key is
154
+ shipped. Private key and customer licenses are never part of the wheel,
155
+ so publishing to the public PyPI is safe. Without a matching license
156
+ file the package is a pure no-op.
157
+
158
+ ### One-time setup
159
+
160
+ 1. Create an account on [PyPI](https://pypi.org/account/register/).
161
+ 2. Generate an API token at *Account settings → API tokens*. Scope it
162
+ to the `cmdbsyncer-enterprise` project once it exists; for the very
163
+ first upload the token must be account-wide.
164
+ 3. Copy the token into `./.pypirc` (gitignored, `chmod 600`) by
165
+ replacing `pypi-REPLACE_WITH_YOUR_TOKEN`:
166
+
167
+ ```ini
168
+ [distutils]
169
+ index-servers =
170
+ pypi
171
+
172
+ [pypi]
173
+ username = __token__
174
+ password = pypi-<your-token>
175
+ ```
176
+
177
+ 4. Install the publishing tools into the venv:
178
+
179
+ ```sh
180
+ pip install --upgrade build twine
181
+ ```
182
+
183
+ ### Release procedure
184
+
185
+ 1. Bump `version` in `pyproject.toml` (PyPI refuses re-uploads of an
186
+ existing version).
187
+ 2. Run the full release pipeline:
188
+
189
+ ```sh
190
+ make release
191
+ ```
192
+
193
+ This runs `clean → build → check → upload → tag` and pushes the
194
+ git tag `v<version>`.
195
+
196
+ Individual targets are available for partial runs:
197
+
198
+ ```sh
199
+ make build # sdist + wheel into dist/
200
+ make check # twine check dist/*
201
+ make upload # twine upload using ./.pypirc
202
+ make tag # git tag & push v<version>
203
+ make clean # remove build artefacts
204
+ make version # print current version
205
+ ```
206
+
207
+ After publication customers install with:
208
+
209
+ ```sh
210
+ pip install cmdbsyncer-enterprise
211
+ ```
212
+
213
+ and still need their individual `license.jwt` for any feature to
214
+ activate.
215
+
216
+ ## Adding a feature
217
+
218
+ 1. **OSS side** — in cmdbsyncer, add a hook call where the feature belongs:
219
+
220
+ ```python
221
+ from application.enterprise import run_hook
222
+ result = run_hook('my_feature', arg1, arg2)
223
+ if result:
224
+ ...
225
+ ```
226
+
227
+ 2. **Enterprise side** — implement the handler:
228
+
229
+ ```text
230
+ src/cmdbsyncer_enterprise/<area>/<module>.py
231
+ ```
232
+
233
+ 3. Wire it into the feature map in
234
+ `src/cmdbsyncer_enterprise/__init__.py`:
235
+
236
+ ```python
237
+ from .<area>.<module> import my_handler
238
+
239
+ _feature_hooks = {
240
+ 'remote_user_login': login_handler,
241
+ 'my_feature': my_handler,
242
+ }
243
+ ```
244
+
245
+ 4. Include `my_feature` in the `--features` list when issuing licenses.
246
+
247
+ ## License
248
+
249
+ Proprietary — Copyright (c) Kuhn & Ruess GmbH. All rights reserved.
250
+ Use requires a valid license agreement.
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cmdbsyncer-enterprise"
7
+ version = "0.1.1"
8
+ description = "Enterprise features for cmdbsyncer (requires license)"
9
+ authors = [{ name = "Kuhn & Ruess GmbH", email = "info@kuhn-ruess.de" }]
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "authlib>=1.3",
13
+ ]
14
+
15
+ [tool.setuptools.packages.find]
16
+ where = ["src"]
17
+
18
+ [tool.setuptools.package-data]
19
+ cmdbsyncer_enterprise = ["public_key.pem"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ """
2
+ cmdbsyncer enterprise add-on.
3
+
4
+ On import, verifies the deployed license file and — if valid — registers the
5
+ licensed features with the OSS `application.enterprise` registry. An invalid
6
+ or expired license raises ImportError so the OSS code falls back cleanly.
7
+ """
8
+ import logging
9
+
10
+ from application.enterprise import register_feature
11
+
12
+ from .license import load_and_verify
13
+ from .auth.remote_user import login_handler
14
+
15
+ log = logging.getLogger(__name__)
16
+
17
+ _license = load_and_verify()
18
+
19
+ _feature_hooks = {
20
+ 'remote_user': login_handler,
21
+ }
22
+
23
+ for _feature in _license.get('features', []):
24
+ hook = _feature_hooks.get(_feature)
25
+ register_feature(_feature, hook)
26
+
27
+ register_feature('license_info', lambda: dict(_license))
28
+
29
+ log.info(
30
+ "cmdbsyncer-enterprise active (license %s, customer %s, expires %s, features %s)",
31
+ _license.get('license_id'),
32
+ _license.get('customer'),
33
+ _license.get('exp'),
34
+ _license.get('features'),
35
+ )
@@ -0,0 +1,20 @@
1
+ """
2
+ Remote user login handler.
3
+
4
+ Invoked by the OSS `auth.views.login` view via the enterprise hook registry.
5
+ Receives the Flask request and returns a matching User instance (or None).
6
+ """
7
+ from application.models.user import User
8
+
9
+
10
+ def login_handler(request):
11
+ """Look up the user named by `request.remote_user`. Returns User or None."""
12
+ name = request.remote_user
13
+ if not name:
14
+ return None
15
+
16
+ user_result = User.objects(name=name, disabled__ne=True)
17
+ if not user_result:
18
+ return None
19
+
20
+ return user_result[0]
@@ -0,0 +1,34 @@
1
+ """
2
+ License loading and verification.
3
+
4
+ Reads a JWT license file from `CMDBSYNCER_LICENSE` (default:
5
+ `/etc/cmdbsyncer/license.jwt`) and verifies it against the embedded RSA
6
+ public key. Any failure (missing file, bad signature, expired) is raised as
7
+ ImportError so the OSS `application.enterprise` graceful-import path kicks in.
8
+ """
9
+ import os
10
+ from pathlib import Path
11
+
12
+ from authlib.jose import jwt, JoseError
13
+
14
+ _PUBKEY_PATH = Path(__file__).parent / "public_key.pem"
15
+ _DEFAULT_LICENSE_PATH = "/etc/cmdbsyncer/license.jwt"
16
+
17
+
18
+ def load_and_verify():
19
+ """Return verified license claims as a dict, or raise ImportError."""
20
+ license_path = os.environ.get('CMDBSYNCER_LICENSE', _DEFAULT_LICENSE_PATH)
21
+
22
+ try:
23
+ public_key = _PUBKEY_PATH.read_bytes()
24
+ token = Path(license_path).read_text(encoding='utf-8').strip()
25
+ except FileNotFoundError as exp:
26
+ raise ImportError(f"Enterprise license not found: {exp}") from exp
27
+
28
+ try:
29
+ claims = jwt.decode(token, public_key)
30
+ claims.validate()
31
+ except JoseError as exp:
32
+ raise ImportError(f"Enterprise license invalid: {exp}") from exp
33
+
34
+ return dict(claims)
@@ -0,0 +1,14 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqguX90vtqNacdU68nTPB
3
+ hb7Etbh65xB354Bf9igqnHlD5qcACJhSnb6YlruvYcuS555YSzHo+wAeWlASGxpl
4
+ YzQnlk5jfIexJWuEPIBucGYypIrgVot22nQOQIeaXPSXBJpJM7l0b6fNlJeKKkt9
5
+ 3brF3p+kG0EmIVQBH9lgSfzyCVDPlGhDFyRmPBcN+5U24OAtzbDOOrUTZCGBHpCx
6
+ +BxkEsDigm4eZy4X2Daa+oXM42gd9pJUW9dsJZcX5Chc6umFr4ZyLSS+YWqLtLzJ
7
+ d+25VtJC14OPmm6XC0p3hD0buRDHKc40I7lCxrKH8DS6TWhfp2hwzDOwX+Ks7N6d
8
+ BrfW/07MAQQ6WG8wOeQTklXsNL5POPWGA9okU2/ZXTKygYFcOYcc8Gux9sEBtt92
9
+ 2ql49aO3yqtA5KUpXg32c7xPPvyJY7J98d782615/r/JJEWVqhd8Y6bKAU3hDIws
10
+ +XWgBikHudMtDg+GyQQ8PToRmeHh96cU3jRZA6vQiyA6ZvcYns6Ok/Y0BYgCpDlX
11
+ Zrv4KJI0n5kdfLYYII4BMAiRyODo8fWj1sZSiBkAYwqQzm8Bix/4D9zRfrSH4z5X
12
+ yg/GPw91MjRhW1OH4PNynjvi0tDtWXPNq6towshuXBcljeFlhwZfSC5dKksFzo6H
13
+ SGFmHhTj3E7urLon5wFrTIMCAwEAAQ==
14
+ -----END PUBLIC KEY-----
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmdbsyncer-enterprise
3
+ Version: 0.1.1
4
+ Summary: Enterprise features for cmdbsyncer (requires license)
5
+ Author-email: Kuhn & Ruess GmbH <info@kuhn-ruess.de>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: authlib>=1.3
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/cmdbsyncer_enterprise/__init__.py
4
+ src/cmdbsyncer_enterprise/license.py
5
+ src/cmdbsyncer_enterprise/public_key.pem
6
+ src/cmdbsyncer_enterprise.egg-info/PKG-INFO
7
+ src/cmdbsyncer_enterprise.egg-info/SOURCES.txt
8
+ src/cmdbsyncer_enterprise.egg-info/dependency_links.txt
9
+ src/cmdbsyncer_enterprise.egg-info/requires.txt
10
+ src/cmdbsyncer_enterprise.egg-info/top_level.txt
11
+ src/cmdbsyncer_enterprise/auth/__init__.py
12
+ src/cmdbsyncer_enterprise/auth/remote_user.py