netsight-sdk 1.0.0__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.
- netsight_sdk-1.0.0/LICENSE +21 -0
- netsight_sdk-1.0.0/PKG-INFO +153 -0
- netsight_sdk-1.0.0/README.md +102 -0
- netsight_sdk-1.0.0/netsight/__init__.py +75 -0
- netsight_sdk-1.0.0/netsight/auth.py +226 -0
- netsight_sdk-1.0.0/netsight/base.py +427 -0
- netsight_sdk-1.0.0/netsight/cli.py +1579 -0
- netsight_sdk-1.0.0/netsight/config.py +307 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/__init__.py +21 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/__init__.py +0 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/auth.toml +3 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/connection.toml +4 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/devices.yaml.example +32 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/resilience.toml +5 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/compiler.py +1007 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/merger.py +109 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/reader.py +290 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/schemas.py +400 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/store.py +250 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/user_config.py +125 -0
- netsight_sdk-1.0.0/netsight/config_mgmt/validator.py +163 -0
- netsight_sdk-1.0.0/netsight/data/__init__.py +22 -0
- netsight_sdk-1.0.0/netsight/data/manager.py +204 -0
- netsight_sdk-1.0.0/netsight/data/models.py +192 -0
- netsight_sdk-1.0.0/netsight/data/store.py +386 -0
- netsight_sdk-1.0.0/netsight/docs/__init__.py +22 -0
- netsight_sdk-1.0.0/netsight/docs/decorators.py +167 -0
- netsight_sdk-1.0.0/netsight/docs/export.py +82 -0
- netsight_sdk-1.0.0/netsight/docs/introspect.py +189 -0
- netsight_sdk-1.0.0/netsight/docs/patterns.py +93 -0
- netsight_sdk-1.0.0/netsight/docs/relations.py +83 -0
- netsight_sdk-1.0.0/netsight/exceptions.py +170 -0
- netsight_sdk-1.0.0/netsight/gate.py +77 -0
- netsight_sdk-1.0.0/netsight/integrations/__init__.py +0 -0
- netsight_sdk-1.0.0/netsight/integrations/base.py +87 -0
- netsight_sdk-1.0.0/netsight/integrations/enrichment/__init__.py +0 -0
- netsight_sdk-1.0.0/netsight/integrations/inventory/__init__.py +0 -0
- netsight_sdk-1.0.0/netsight/integrations/inventory/yaml_file.py +58 -0
- netsight_sdk-1.0.0/netsight/integrations/registry.py +193 -0
- netsight_sdk-1.0.0/netsight/integrations/sinks/__init__.py +0 -0
- netsight_sdk-1.0.0/netsight/integrations/sinks/json_file.py +115 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/__init__.py +1 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/cache.py +74 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/cli.py +36 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/doctor.py +387 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/path_security.py +270 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/__init__.py +70 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/allowlists.py +232 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/api.py +100 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/conventions.py +113 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/docs.py +194 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/fixtures.py +62 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/operations.py +229 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/packs.py +331 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/parsers.py +249 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/patterns.py +73 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/recipes.py +161 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/registries.py +242 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/security.py +76 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/source.py +242 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/resources/vendors.py +387 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/server.py +129 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/tools/__init__.py +55 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/tools/inspection.py +823 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/tools/overview.py +315 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/tools/recipes.py +70 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/tools/scaffolding.py +904 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/tools/security.py +131 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/tools/validation.py +291 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/transport.py +161 -0
- netsight_sdk-1.0.0/netsight/mcp_dev/watch.py +166 -0
- netsight_sdk-1.0.0/netsight/output.py +178 -0
- netsight_sdk-1.0.0/netsight/packs/__init__.py +61 -0
- netsight_sdk-1.0.0/netsight/packs/_index.toml +18 -0
- netsight_sdk-1.0.0/netsight/packs/_traversable.py +92 -0
- netsight_sdk-1.0.0/netsight/packs/allowlist.py +180 -0
- netsight_sdk-1.0.0/netsight/packs/allowlist_cli.py +435 -0
- netsight_sdk-1.0.0/netsight/packs/cli.py +602 -0
- netsight_sdk-1.0.0/netsight/packs/exceptions.py +103 -0
- netsight_sdk-1.0.0/netsight/packs/index.py +108 -0
- netsight_sdk-1.0.0/netsight/packs/loader.py +152 -0
- netsight_sdk-1.0.0/netsight/packs/registry.py +406 -0
- netsight_sdk-1.0.0/netsight/parsers/__init__.py +66 -0
- netsight_sdk-1.0.0/netsight/parsers/base.py +300 -0
- netsight_sdk-1.0.0/netsight/parsers/engine.py +67 -0
- netsight_sdk-1.0.0/netsight/parsers/engines/__init__.py +1 -0
- netsight_sdk-1.0.0/netsight/parsers/engines/jmespath_engine.py +232 -0
- netsight_sdk-1.0.0/netsight/parsers/engines/passthrough.py +27 -0
- netsight_sdk-1.0.0/netsight/parsers/exceptions.py +25 -0
- netsight_sdk-1.0.0/netsight/parsers/loader.py +128 -0
- netsight_sdk-1.0.0/netsight/parsers/registry.py +130 -0
- netsight_sdk-1.0.0/netsight/parsers/resolver.py +151 -0
- netsight_sdk-1.0.0/netsight/parsers/spec.py +121 -0
- netsight_sdk-1.0.0/netsight/parsers/transforms.py +148 -0
- netsight_sdk-1.0.0/netsight/parsers/types.py +176 -0
- netsight_sdk-1.0.0/netsight/py.typed +0 -0
- netsight_sdk-1.0.0/netsight/registry.py +404 -0
- netsight_sdk-1.0.0/netsight/resilience.py +211 -0
- netsight_sdk-1.0.0/netsight/sdk/__init__.py +19 -0
- netsight_sdk-1.0.0/netsight/sdk/client.py +205 -0
- netsight_sdk-1.0.0/netsight/sdk/device.py +205 -0
- netsight_sdk-1.0.0/netsight/sdk/local.py +296 -0
- netsight_sdk-1.0.0/netsight/sdk/remote.py +242 -0
- netsight_sdk-1.0.0/netsight/sdk/types.py +18 -0
- netsight_sdk-1.0.0/netsight/security/__init__.py +1 -0
- netsight_sdk-1.0.0/netsight/security/audit.py +98 -0
- netsight_sdk-1.0.0/netsight/security/code_patterns.py +297 -0
- netsight_sdk-1.0.0/netsight/security/dependencies.py +137 -0
- netsight_sdk-1.0.0/netsight/security/findings.py +37 -0
- netsight_sdk-1.0.0/netsight_sdk.egg-info/PKG-INFO +153 -0
- netsight_sdk-1.0.0/netsight_sdk.egg-info/SOURCES.txt +115 -0
- netsight_sdk-1.0.0/netsight_sdk.egg-info/dependency_links.txt +1 -0
- netsight_sdk-1.0.0/netsight_sdk.egg-info/entry_points.txt +2 -0
- netsight_sdk-1.0.0/netsight_sdk.egg-info/requires.txt +24 -0
- netsight_sdk-1.0.0/netsight_sdk.egg-info/top_level.txt +1 -0
- netsight_sdk-1.0.0/pyproject.toml +170 -0
- netsight_sdk-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NetSight contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netsight-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Read-only multi-vendor network device query SDK (core library)
|
|
5
|
+
Author: NetSight contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/magicboxlab-ai/netsight-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/magicboxlab-ai/netsight-sdk
|
|
9
|
+
Project-URL: Changelog, https://github.com/magicboxlab-ai/netsight-sdk/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/magicboxlab-ai/netsight-sdk/issues
|
|
11
|
+
Project-URL: Documentation, https://github.com/magicboxlab-ai/netsight-sdk/blob/main/docs/guides/user-guide.md
|
|
12
|
+
Keywords: network,automation,sdk,palo-alto,panos,read-only,multi-vendor
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: Operating System :: POSIX
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: System :: Networking
|
|
21
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
22
|
+
Classifier: Topic :: System :: Systems Administration
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.14
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: requests>=2.31.0
|
|
28
|
+
Requires-Dist: pyyaml>=6.0
|
|
29
|
+
Requires-Dist: tabulate>=0.9.0
|
|
30
|
+
Requires-Dist: rich>=13.0.0
|
|
31
|
+
Requires-Dist: xmltodict>=0.13.0
|
|
32
|
+
Requires-Dist: pydantic>=2.0
|
|
33
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
34
|
+
Requires-Dist: jmespath>=1.0.1
|
|
35
|
+
Requires-Dist: pip-audit>=2.7.0
|
|
36
|
+
Requires-Dist: packaging>=23.0
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
41
|
+
Requires-Dist: httpx>=0.24.0; extra == "dev"
|
|
42
|
+
Requires-Dist: mypy>=1.5; extra == "dev"
|
|
43
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
44
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
45
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
46
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
47
|
+
Requires-Dist: types-tabulate; extra == "dev"
|
|
48
|
+
Requires-Dist: types-jmespath; extra == "dev"
|
|
49
|
+
Requires-Dist: watchdog>=4.0.0; extra == "dev"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# NetSight
|
|
53
|
+
|
|
54
|
+
**NetSight** is a read-only, multi-vendor network device query SDK written in Python 3.14. It connects to devices over vendor-native APIs (currently PAN-OS XML; more vendors planned), enforces a deny-all command allowlist, and returns structured data parsed from declarative YAML parser specs.
|
|
55
|
+
|
|
56
|
+
## For users
|
|
57
|
+
|
|
58
|
+
See the [**User Guide**](docs/guides/user-guide.md) for installation, configuration, and day-to-day SDK usage — connecting to a device, running an operation, handling results, writing a template workflow.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from netsight import NetSight
|
|
62
|
+
|
|
63
|
+
ns = NetSight(config="config/devices.yaml")
|
|
64
|
+
device = ns.device("fw-01")
|
|
65
|
+
info = device.show_system_info()
|
|
66
|
+
routes = device.show_routing_table()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Installing the core SDK
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
# Minimal SDK install — just the device-query core
|
|
73
|
+
pip install netsight-sdk
|
|
74
|
+
|
|
75
|
+
# With the dev MCP server (for Claude Code contributors)
|
|
76
|
+
pip install 'netsight-sdk[dev]'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or, to track the development branch directly:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
pip install git+https://github.com/magicboxlab-ai/netsight-sdk.git
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### First-time setup
|
|
86
|
+
|
|
87
|
+
After installing the core SDK, run `netsight init` to create your device
|
|
88
|
+
configuration file:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
# Create ~/.config/netsight/devices.yaml with a template
|
|
92
|
+
netsight init
|
|
93
|
+
|
|
94
|
+
# Edit the file to add your devices, then verify
|
|
95
|
+
netsight doctor
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The config file search order is: `$NETSIGHT_CONFIG_DIR` → `./config/`
|
|
99
|
+
(cwd, for project-local dev workflows) → `$XDG_CONFIG_HOME/netsight/`
|
|
100
|
+
→ `~/.config/netsight/`.
|
|
101
|
+
|
|
102
|
+
### Installing vendor packs
|
|
103
|
+
|
|
104
|
+
The core SDK is vendor-agnostic. Vendor support ships as separate pack packages installed alongside the core. Use the `netsight pack` CLI to discover and install packs:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
# List packs available in the bundled catalog
|
|
108
|
+
netsight pack list --available
|
|
109
|
+
|
|
110
|
+
# Install a pack
|
|
111
|
+
netsight pack install paloalto-firewall-xml
|
|
112
|
+
|
|
113
|
+
# Verify everything loaded correctly
|
|
114
|
+
netsight doctor
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Packs can also be installed directly from their source repositories:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
pip install git+https://github.com/magicboxlab-ai/netsight-packs-paloalto.git#subdirectory=packs/paloalto-firewall-xml
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
After installation, the pack is auto-discovered at startup via the `netsight.packs` entry-point group — no config change required.
|
|
124
|
+
|
|
125
|
+
See [**User Guide → Vendor packs**](docs/guides/user-guide.md#vendor-packs) for the full install and configuration flow.
|
|
126
|
+
|
|
127
|
+
## For contributors
|
|
128
|
+
|
|
129
|
+
NetSight ships a purpose-built MCP server (`netsight-dev`) that exposes the codebase to Claude Code as structured resources and validation tools. The bootstrap flow for a new clone is documented end-to-end in the developer guide:
|
|
130
|
+
|
|
131
|
+
See [**Developer Guide → Getting Started for Development**](docs/guides/developer-guide.md#getting-started-for-development).
|
|
132
|
+
|
|
133
|
+
**TL;DR:**
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
git clone <repo-url> netsight
|
|
137
|
+
cd netsight
|
|
138
|
+
python3.14 -m venv .venv
|
|
139
|
+
source .venv/bin/activate
|
|
140
|
+
pip install -e .
|
|
141
|
+
netsight init --local # creates ./config/devices.yaml from the template
|
|
142
|
+
netsight doctor # verifies the dev env end-to-end
|
|
143
|
+
claude # approve the netsight-dev MCP server when prompted
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The rest of the developer guide covers adding vendor packs, writing parser specs, custom types and transforms, registry wiring, self-documentation conventions, testing, and the release workflow.
|
|
147
|
+
|
|
148
|
+
## Project layout at a glance
|
|
149
|
+
|
|
150
|
+
- `netsight/` — SDK source (pack registry, parsers, config management, security module, dev MCP server).
|
|
151
|
+
- `UNITTEST/` — pytest test suite (not `tests/`).
|
|
152
|
+
- `TOOLS/` — shell utilities (`full_test_cycle.sh`, `security_check.sh`).
|
|
153
|
+
- `docs/guides/` — user guide and developer guide.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# NetSight
|
|
2
|
+
|
|
3
|
+
**NetSight** is a read-only, multi-vendor network device query SDK written in Python 3.14. It connects to devices over vendor-native APIs (currently PAN-OS XML; more vendors planned), enforces a deny-all command allowlist, and returns structured data parsed from declarative YAML parser specs.
|
|
4
|
+
|
|
5
|
+
## For users
|
|
6
|
+
|
|
7
|
+
See the [**User Guide**](docs/guides/user-guide.md) for installation, configuration, and day-to-day SDK usage — connecting to a device, running an operation, handling results, writing a template workflow.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from netsight import NetSight
|
|
11
|
+
|
|
12
|
+
ns = NetSight(config="config/devices.yaml")
|
|
13
|
+
device = ns.device("fw-01")
|
|
14
|
+
info = device.show_system_info()
|
|
15
|
+
routes = device.show_routing_table()
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Installing the core SDK
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
# Minimal SDK install — just the device-query core
|
|
22
|
+
pip install netsight-sdk
|
|
23
|
+
|
|
24
|
+
# With the dev MCP server (for Claude Code contributors)
|
|
25
|
+
pip install 'netsight-sdk[dev]'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or, to track the development branch directly:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install git+https://github.com/magicboxlab-ai/netsight-sdk.git
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### First-time setup
|
|
35
|
+
|
|
36
|
+
After installing the core SDK, run `netsight init` to create your device
|
|
37
|
+
configuration file:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
# Create ~/.config/netsight/devices.yaml with a template
|
|
41
|
+
netsight init
|
|
42
|
+
|
|
43
|
+
# Edit the file to add your devices, then verify
|
|
44
|
+
netsight doctor
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The config file search order is: `$NETSIGHT_CONFIG_DIR` → `./config/`
|
|
48
|
+
(cwd, for project-local dev workflows) → `$XDG_CONFIG_HOME/netsight/`
|
|
49
|
+
→ `~/.config/netsight/`.
|
|
50
|
+
|
|
51
|
+
### Installing vendor packs
|
|
52
|
+
|
|
53
|
+
The core SDK is vendor-agnostic. Vendor support ships as separate pack packages installed alongside the core. Use the `netsight pack` CLI to discover and install packs:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
# List packs available in the bundled catalog
|
|
57
|
+
netsight pack list --available
|
|
58
|
+
|
|
59
|
+
# Install a pack
|
|
60
|
+
netsight pack install paloalto-firewall-xml
|
|
61
|
+
|
|
62
|
+
# Verify everything loaded correctly
|
|
63
|
+
netsight doctor
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Packs can also be installed directly from their source repositories:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
pip install git+https://github.com/magicboxlab-ai/netsight-packs-paloalto.git#subdirectory=packs/paloalto-firewall-xml
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
After installation, the pack is auto-discovered at startup via the `netsight.packs` entry-point group — no config change required.
|
|
73
|
+
|
|
74
|
+
See [**User Guide → Vendor packs**](docs/guides/user-guide.md#vendor-packs) for the full install and configuration flow.
|
|
75
|
+
|
|
76
|
+
## For contributors
|
|
77
|
+
|
|
78
|
+
NetSight ships a purpose-built MCP server (`netsight-dev`) that exposes the codebase to Claude Code as structured resources and validation tools. The bootstrap flow for a new clone is documented end-to-end in the developer guide:
|
|
79
|
+
|
|
80
|
+
See [**Developer Guide → Getting Started for Development**](docs/guides/developer-guide.md#getting-started-for-development).
|
|
81
|
+
|
|
82
|
+
**TL;DR:**
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
git clone <repo-url> netsight
|
|
86
|
+
cd netsight
|
|
87
|
+
python3.14 -m venv .venv
|
|
88
|
+
source .venv/bin/activate
|
|
89
|
+
pip install -e .
|
|
90
|
+
netsight init --local # creates ./config/devices.yaml from the template
|
|
91
|
+
netsight doctor # verifies the dev env end-to-end
|
|
92
|
+
claude # approve the netsight-dev MCP server when prompted
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The rest of the developer guide covers adding vendor packs, writing parser specs, custom types and transforms, registry wiring, self-documentation conventions, testing, and the release workflow.
|
|
96
|
+
|
|
97
|
+
## Project layout at a glance
|
|
98
|
+
|
|
99
|
+
- `netsight/` — SDK source (pack registry, parsers, config management, security module, dev MCP server).
|
|
100
|
+
- `UNITTEST/` — pytest test suite (not `tests/`).
|
|
101
|
+
- `TOOLS/` — shell utilities (`full_test_cycle.sh`, `security_check.sh`).
|
|
102
|
+
- `docs/guides/` — user guide and developer guide.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""NetSight — Read-only multi-vendor network device query SDK.
|
|
2
|
+
|
|
3
|
+
The importable Python package is ``netsight`` (unchanged). The PyPI
|
|
4
|
+
distribution that ships this package is ``netsight-sdk``. Other
|
|
5
|
+
distributions — ``netsight-ops`` (runtime service platform) and
|
|
6
|
+
vendor packs such as ``netsight-pack-paloalto-firewall-xml`` —
|
|
7
|
+
depend on this core via standard pip dependencies.
|
|
8
|
+
|
|
9
|
+
The ``__version__`` string below is read from the installed
|
|
10
|
+
distribution metadata at import time, so it always matches what
|
|
11
|
+
``pip show netsight-sdk`` reports. A source-tree import without an
|
|
12
|
+
install (e.g. during bootstrap of a fresh checkout) falls back to
|
|
13
|
+
the compile-time default declared in ``pyproject.toml``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
__version__: str = _pkg_version("netsight-sdk")
|
|
20
|
+
except PackageNotFoundError: # pragma: no cover — source tree without install
|
|
21
|
+
__version__ = "1.0.0"
|
|
22
|
+
|
|
23
|
+
from netsight.config import DeviceConfig
|
|
24
|
+
from netsight.data.models import DiffResult, QueryResult, Snapshot
|
|
25
|
+
from netsight.exceptions import (
|
|
26
|
+
AuthenticationError,
|
|
27
|
+
CommandDeniedError,
|
|
28
|
+
ConfigValidationError,
|
|
29
|
+
DeviceConnectionError,
|
|
30
|
+
NetSightError,
|
|
31
|
+
PluginNotFoundError,
|
|
32
|
+
RateLimitError,
|
|
33
|
+
ResponseSizeError,
|
|
34
|
+
)
|
|
35
|
+
from netsight.sdk.client import NetSight
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def bootstrap() -> None:
|
|
39
|
+
"""Discover and register every installed NetSight pack.
|
|
40
|
+
|
|
41
|
+
Idempotent public entry point for pack discovery. Call this from any
|
|
42
|
+
embedding that does not go through :class:`~netsight.sdk.local.LocalBackend`,
|
|
43
|
+
the dev MCP server, or the ``netsight`` CLI — typically a custom
|
|
44
|
+
script that imports :class:`NetSight` and wants installed packs to be
|
|
45
|
+
usable before the first device query.
|
|
46
|
+
|
|
47
|
+
The underlying :meth:`netsight.packs.registry.PackRegistry.ensure_loaded`
|
|
48
|
+
uses double-checked locking and the ``_loaded`` flag to guarantee that
|
|
49
|
+
entry-point discovery runs exactly once per process, so calling
|
|
50
|
+
``bootstrap()`` multiple times (from multiple entry points) is cheap
|
|
51
|
+
and safe. Per-pack registration errors are absorbed and surfaced via
|
|
52
|
+
:meth:`PackRegistry.load_errors` rather than raised.
|
|
53
|
+
"""
|
|
54
|
+
from netsight.packs import pack_registry
|
|
55
|
+
|
|
56
|
+
pack_registry.ensure_loaded()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"__version__",
|
|
61
|
+
"NetSight",
|
|
62
|
+
"DeviceConfig",
|
|
63
|
+
"QueryResult",
|
|
64
|
+
"Snapshot",
|
|
65
|
+
"DiffResult",
|
|
66
|
+
"AuthenticationError",
|
|
67
|
+
"CommandDeniedError",
|
|
68
|
+
"ConfigValidationError",
|
|
69
|
+
"DeviceConnectionError",
|
|
70
|
+
"NetSightError",
|
|
71
|
+
"PluginNotFoundError",
|
|
72
|
+
"RateLimitError",
|
|
73
|
+
"ResponseSizeError",
|
|
74
|
+
"bootstrap",
|
|
75
|
+
]
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Authentication strategy and token-caching manager for NetSight.
|
|
2
|
+
|
|
3
|
+
Design
|
|
4
|
+
------
|
|
5
|
+
``AuthStrategy`` is an ABC that vendor-specific adapters implement — they
|
|
6
|
+
handle the actual network call to obtain or revoke a session token.
|
|
7
|
+
|
|
8
|
+
``AuthManager`` owns a per-host token cache and enforces TTL-based expiry.
|
|
9
|
+
It deliberately never exposes raw token values in ``repr`` or log output.
|
|
10
|
+
|
|
11
|
+
Usage example::
|
|
12
|
+
|
|
13
|
+
class MyStrategy(AuthStrategy):
|
|
14
|
+
def acquire_token(self, host, username, password):
|
|
15
|
+
... # POST /login, return token string
|
|
16
|
+
def revoke_token(self, host, token):
|
|
17
|
+
... # POST /logout
|
|
18
|
+
|
|
19
|
+
manager = AuthManager(MyStrategy(), token_ttl_seconds=1800)
|
|
20
|
+
token = manager.get_token("192.168.1.1", "admin", "hunter2")
|
|
21
|
+
manager.close()
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import time
|
|
28
|
+
from abc import ABC, abstractmethod
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Abstract strategy
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AuthStrategy(ABC):
|
|
39
|
+
"""Contract that every authentication back-end must satisfy."""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def acquire_token(self, host: str, username: str, password: str) -> str:
|
|
43
|
+
"""Obtain a new session token from *host*.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
host:
|
|
48
|
+
Hostname or IP address of the target device.
|
|
49
|
+
username:
|
|
50
|
+
Account name to authenticate with.
|
|
51
|
+
password:
|
|
52
|
+
Credential. Implementations must not log this value.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
str
|
|
57
|
+
An opaque token string suitable for subsequent API calls.
|
|
58
|
+
|
|
59
|
+
Raises
|
|
60
|
+
------
|
|
61
|
+
netsight.exceptions.AuthenticationError
|
|
62
|
+
If the device rejects the credentials.
|
|
63
|
+
Any network/transport exception the caller may choose to handle.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def revoke_token(self, host: str, token: str) -> None:
|
|
68
|
+
"""Invalidate *token* on *host*.
|
|
69
|
+
|
|
70
|
+
Implementations should be best-effort; ``AuthManager`` handles
|
|
71
|
+
failures by logging a warning rather than propagating exceptions.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
host:
|
|
76
|
+
Same host that issued the token.
|
|
77
|
+
token:
|
|
78
|
+
The token to invalidate.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# Internal cached-token container
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class _CachedToken:
|
|
88
|
+
"""Lightweight container for a token and its acquisition timestamp.
|
|
89
|
+
|
|
90
|
+
Uses ``__slots__`` to minimise per-instance memory overhead — there may
|
|
91
|
+
be hundreds of these alive at once in a large environment.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
__slots__ = ("token", "acquired_at")
|
|
95
|
+
|
|
96
|
+
def __init__(self, token: str) -> None:
|
|
97
|
+
self.token: str = token
|
|
98
|
+
self.acquired_at: float = time.monotonic()
|
|
99
|
+
|
|
100
|
+
def is_expired(self, ttl: float) -> bool:
|
|
101
|
+
"""Return ``True`` if the token is older than *ttl* seconds."""
|
|
102
|
+
return (time.monotonic() - self.acquired_at) >= ttl
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# AuthManager
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class AuthManager:
|
|
111
|
+
"""Caching authentication manager.
|
|
112
|
+
|
|
113
|
+
Maintains one valid token per host; transparently reacquires when the
|
|
114
|
+
TTL expires or after an explicit ``invalidate`` call.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
strategy:
|
|
119
|
+
The ``AuthStrategy`` instance used to acquire/revoke tokens.
|
|
120
|
+
token_ttl_seconds:
|
|
121
|
+
How long (in seconds) a cached token is considered valid before a
|
|
122
|
+
new one is acquired. Defaults to 3600 (one hour).
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self, strategy: AuthStrategy, token_ttl_seconds: float = 3600
|
|
127
|
+
) -> None:
|
|
128
|
+
self._strategy = strategy
|
|
129
|
+
self._token_ttl: float = token_ttl_seconds
|
|
130
|
+
# Maps host -> _CachedToken
|
|
131
|
+
self._cache: dict[str, _CachedToken] = {}
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
# Public API
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
def get_token(self, host: str, username: str, password: str) -> str:
|
|
138
|
+
"""Return a valid token for *host*, acquiring one if necessary.
|
|
139
|
+
|
|
140
|
+
The token is taken from the in-memory cache when it exists and has
|
|
141
|
+
not exceeded the TTL. Otherwise a new token is acquired from the
|
|
142
|
+
strategy.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
host:
|
|
147
|
+
Target device hostname or IP.
|
|
148
|
+
username:
|
|
149
|
+
Credential — passed through to the strategy unchanged.
|
|
150
|
+
password:
|
|
151
|
+
Credential — never logged.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
str
|
|
156
|
+
A non-empty token string.
|
|
157
|
+
"""
|
|
158
|
+
cached = self._cache.get(host)
|
|
159
|
+
if cached is not None and not cached.is_expired(self._token_ttl):
|
|
160
|
+
logger.debug("Using cached token for host=%s", host)
|
|
161
|
+
return cached.token
|
|
162
|
+
|
|
163
|
+
logger.info("Acquiring new token for host=%s", host)
|
|
164
|
+
token = self._strategy.acquire_token(host, username, password)
|
|
165
|
+
self._cache[host] = _CachedToken(token)
|
|
166
|
+
return token
|
|
167
|
+
|
|
168
|
+
def invalidate(self, host: str) -> None:
|
|
169
|
+
"""Remove the cached token for *host* and attempt to revoke it.
|
|
170
|
+
|
|
171
|
+
If the strategy raises during revocation the exception is caught
|
|
172
|
+
and a ``WARNING`` is emitted; the cache entry is removed regardless.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
host:
|
|
177
|
+
The host whose token should be invalidated.
|
|
178
|
+
"""
|
|
179
|
+
cached = self._cache.pop(host, None)
|
|
180
|
+
if cached is None:
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
self._strategy.revoke_token(host, cached.token)
|
|
185
|
+
except Exception as exc: # noqa: BLE001
|
|
186
|
+
logger.warning(
|
|
187
|
+
"Failed to revoke token for host=%s: %s", host, exc
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def has_token(self, host: str) -> bool:
|
|
191
|
+
"""Return ``True`` if a non-expired token is cached for *host*.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
host:
|
|
196
|
+
The host to query.
|
|
197
|
+
"""
|
|
198
|
+
cached = self._cache.get(host)
|
|
199
|
+
if cached is None:
|
|
200
|
+
return False
|
|
201
|
+
return not cached.is_expired(self._token_ttl)
|
|
202
|
+
|
|
203
|
+
def close(self) -> None:
|
|
204
|
+
"""Revoke all cached tokens and clear the cache.
|
|
205
|
+
|
|
206
|
+
Revocation failures are logged at ``WARNING`` and do not prevent the
|
|
207
|
+
remaining tokens from being processed.
|
|
208
|
+
"""
|
|
209
|
+
# Snapshot keys so we can safely mutate the dict during iteration
|
|
210
|
+
hosts = list(self._cache.keys())
|
|
211
|
+
for host in hosts:
|
|
212
|
+
self.invalidate(host)
|
|
213
|
+
|
|
214
|
+
# ------------------------------------------------------------------
|
|
215
|
+
# Dunder helpers
|
|
216
|
+
# ------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
def __repr__(self) -> str:
|
|
219
|
+
"""Return a safe representation that never includes token values."""
|
|
220
|
+
hosts = list(self._cache.keys())
|
|
221
|
+
strategy_name = type(self._strategy).__name__
|
|
222
|
+
return (
|
|
223
|
+
f"AuthManager(strategy={strategy_name}, "
|
|
224
|
+
f"hosts={hosts!r}, "
|
|
225
|
+
f"ttl={self._token_ttl}s)"
|
|
226
|
+
)
|