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.
- cmdbsyncer_enterprise-0.1.1/PKG-INFO +7 -0
- cmdbsyncer_enterprise-0.1.1/README.md +250 -0
- cmdbsyncer_enterprise-0.1.1/pyproject.toml +19 -0
- cmdbsyncer_enterprise-0.1.1/setup.cfg +4 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise/__init__.py +35 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise/auth/__init__.py +0 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise/auth/remote_user.py +20 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise/license.py +34 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise/public_key.pem +14 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise.egg-info/PKG-INFO +7 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise.egg-info/SOURCES.txt +12 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise.egg-info/dependency_links.txt +1 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise.egg-info/requires.txt +1 -0
- cmdbsyncer_enterprise-0.1.1/src/cmdbsyncer_enterprise.egg-info/top_level.txt +1 -0
|
@@ -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,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
|
+
)
|
|
File without changes
|
|
@@ -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,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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
authlib>=1.3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cmdbsyncer_enterprise
|