splunkctl 0.4.0__tar.gz → 0.5.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.
- splunkctl-0.5.0/PKG-INFO +256 -0
- splunkctl-0.5.0/README.md +218 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/pyproject.toml +7 -2
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/__init__.py +1 -1
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/common.py +23 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/conf.py +13 -17
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/doctor.py +43 -25
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/knowledge.py +15 -32
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/lookups.py +3 -13
- splunkctl-0.5.0/splunkctl/commands/mcp_cmd.py +47 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/rules.py +1 -1
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/rules_io.py +3 -17
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/search.py +1 -1
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/state_io.py +2 -1
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/users.py +19 -14
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/main.py +2 -2
- splunkctl-0.5.0/splunkctl/mcp/__init__.py +0 -0
- splunkctl-0.5.0/splunkctl/mcp/resources.py +35 -0
- splunkctl-0.5.0/splunkctl/mcp/server.py +328 -0
- splunkctl-0.5.0/splunkctl/mcp/tools.py +233 -0
- splunkctl-0.5.0/splunkctl.egg-info/PKG-INFO +256 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl.egg-info/SOURCES.txt +5 -2
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl.egg-info/requires.txt +1 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/tests/test_version.py +1 -1
- splunkctl-0.4.0/PKG-INFO +0 -194
- splunkctl-0.4.0/README.md +0 -157
- splunkctl-0.4.0/splunkctl/commands/skill_cmd.py +0 -43
- splunkctl-0.4.0/splunkctl/skill/SKILL.md +0 -1045
- splunkctl-0.4.0/splunkctl.egg-info/PKG-INFO +0 -194
- {splunkctl-0.4.0 → splunkctl-0.5.0}/LICENSE +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/setup.cfg +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/__main__.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/client.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/__init__.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/alerts.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/apps.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/audit.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/audit_parse.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/commands_meta.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/conf_ops.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/config_cmd.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/dashboards.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/datamodels.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/es.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/hec.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/indexes.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/info.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/inputs.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/kvstore.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/lookups_wiring.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/parsers.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/parsers_io.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/server.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/state.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/state_io_blobs.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/state_io_confs.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/commands/state_types.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/config.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/errors.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/guard.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl/output.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl.egg-info/dependency_links.txt +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl.egg-info/entry_points.txt +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/splunkctl.egg-info/top_level.txt +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/tests/test_client.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/tests/test_config.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/tests/test_guard.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/tests/test_main_errors.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/tests/test_main_hoisting.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.5.0}/tests/test_output.py +0 -0
splunkctl-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: splunkctl
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: CLI tool for Splunk Enterprise SIEM operations.
|
|
5
|
+
Author: dannyota
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/dannyota/splunkctl
|
|
8
|
+
Project-URL: Repository, https://github.com/dannyota/splunkctl
|
|
9
|
+
Project-URL: Issues, https://github.com/dannyota/splunkctl/issues
|
|
10
|
+
Keywords: splunk,siem,security,cli,detection,spl
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: System :: Systems Administration
|
|
19
|
+
Requires-Python: >=3.13
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: splunk-sdk>=2.0
|
|
23
|
+
Requires-Dist: click>=8.2
|
|
24
|
+
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: tabulate>=0.9
|
|
26
|
+
Requires-Dist: requests>=2.32
|
|
27
|
+
Requires-Dist: defusedxml>=0.7
|
|
28
|
+
Requires-Dist: mcp>=1.23
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
33
|
+
Requires-Dist: types-requests; extra == "dev"
|
|
34
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
35
|
+
Requires-Dist: types-tabulate; extra == "dev"
|
|
36
|
+
Requires-Dist: types-defusedxml; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
<div align="center">
|
|
40
|
+
|
|
41
|
+
<a href="https://splunk.danny.vn"><img src="docs/assets/banner.svg" alt="splunkctl — operate Splunk Enterprise as code" width="600"></a>
|
|
42
|
+
|
|
43
|
+
# splunkctl
|
|
44
|
+
|
|
45
|
+
**Operate Splunk Enterprise as code — for SOC teams, detection engineers, and AI agents.**
|
|
46
|
+
|
|
47
|
+
[Docs](https://splunk.danny.vn) · [Catalog](docs/design/catalog.md) · [Releases](https://github.com/dannyota/splunkctl/releases)
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
A Python CLI that queries, inspects, and manages a **remote** Splunk
|
|
54
|
+
Enterprise instance over the REST API. Built on the
|
|
55
|
+
[splunk-sdk-python](https://github.com/dannyota/splunk-sdk-python/tree/splunkctl)
|
|
56
|
+
fork with [Click](https://click.palletsprojects.com/). The core loop is
|
|
57
|
+
**pull live state → review the diff → push it back** — one `state` engine
|
|
58
|
+
covering rules, parsers, macros, lookups, and dashboards, with a
|
|
59
|
+
change-evidence report artifact for bank change tickets. It's built for
|
|
60
|
+
humans and LLM agents alike: deterministic flags, `--json` everywhere,
|
|
61
|
+
structured error envelopes, and a built-in MCP server
|
|
62
|
+
(`splunkctl mcp serve`) with progressive tool discovery.
|
|
63
|
+
|
|
64
|
+
> **Every mutation is dry-run by default.** Nothing changes until you pass
|
|
65
|
+
> `--yes`. Always preview, read it, then apply.
|
|
66
|
+
|
|
67
|
+
## What it does
|
|
68
|
+
|
|
69
|
+
- **Config as code** — `state pull` → edit → `state diff` → `state push`
|
|
70
|
+
across rules, parsers, macros, lookups, and dashboards (diff-only). Push
|
|
71
|
+
writes a before→after JSON report artifact usable as change-ticket
|
|
72
|
+
evidence. Push never deletes.
|
|
73
|
+
- **Detection engineering** — rules CRUD + YAML import/export, macros,
|
|
74
|
+
eventtypes, tags, data model acceleration health, lookup definitions +
|
|
75
|
+
automatic lookups (transforms.conf/props.conf wiring), and first-class
|
|
76
|
+
`--email-to`/`--webhook-url` alert-action flags.
|
|
77
|
+
- **ES incident review** — `es notables list/get/update` for the SOC
|
|
78
|
+
triage loop (status, owner, urgency, disposition, comment via
|
|
79
|
+
`notable_update`); feature-detected on Enterprise Security.
|
|
80
|
+
- **Compliance & audit** — `audit changes` normalizes both `_audit` event
|
|
81
|
+
shapes into one schema; `audit rbac` produces a users × roles ×
|
|
82
|
+
capabilities attestation view for access recertification.
|
|
83
|
+
- **KV store** — collection + document CRUD, JSONL import/export with
|
|
84
|
+
500-doc batch chunking, query with server-side filtering.
|
|
85
|
+
- **Topology health** — `server cluster/shcluster/deployment` reads
|
|
86
|
+
distinguish "no threat" from "an indexer is down" in clustered
|
|
87
|
+
deployments.
|
|
88
|
+
- **Agent reliability** — structured JSON error envelope with typed
|
|
89
|
+
taxonomy (`auth`/`permission`/`not_found`/`timeout`/...), uniform
|
|
90
|
+
`--limit`/`--offset`/`--filter` on every list surface, multi-instance
|
|
91
|
+
profiles with a bank-safety guard banner
|
|
92
|
+
(`(profile: uat @ host:port)`).
|
|
93
|
+
- **Built for agents** — built-in MCP server with 129 auto-generated tools,
|
|
94
|
+
progressive discovery (5 meta-tools + focus/unfocus), 22 guide resources,
|
|
95
|
+
guard markers on every mutation, dual output (TTY = table, pipe = JSON).
|
|
96
|
+
|
|
97
|
+
## Install
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install splunkctl
|
|
101
|
+
pip install git+https://github.com/dannyota/splunk-sdk-python@splunkctl
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Requires Python 3.13+. The second line installs the
|
|
105
|
+
[forked SDK](https://github.com/dannyota/splunk-sdk-python/tree/splunkctl)
|
|
106
|
+
which adds dashboard, lookup, and HEC token entity classes. Without it,
|
|
107
|
+
core commands (search, rules, alerts, indexes, inputs, apps, users) still
|
|
108
|
+
work.
|
|
109
|
+
|
|
110
|
+
### Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/dannyota/splunkctl
|
|
114
|
+
cd splunkctl
|
|
115
|
+
pip install -e '.[dev]'
|
|
116
|
+
splunkctl --version
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Quickstart
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
splunkctl config init # interactive setup
|
|
123
|
+
splunkctl doctor # check connection, auth, permissions
|
|
124
|
+
splunkctl search run 'index=main | head 10' # run a search
|
|
125
|
+
splunkctl rules list # list detection rules
|
|
126
|
+
splunkctl commands --json # discover every verb
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## CLI usage
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Read
|
|
133
|
+
splunkctl rules list --app Splunk_Security_Essentials --json
|
|
134
|
+
splunkctl alerts list --json
|
|
135
|
+
splunkctl datamodels acceleration
|
|
136
|
+
splunkctl audit rbac --format csv --out rbac.csv
|
|
137
|
+
|
|
138
|
+
# Mutate (dry-run first, --yes to apply)
|
|
139
|
+
splunkctl rules disable 'My Rule' # preview
|
|
140
|
+
splunkctl rules disable 'My Rule' --yes # apply
|
|
141
|
+
splunkctl es notables update <id> --status closed --owner analyst --yes
|
|
142
|
+
|
|
143
|
+
# Config-as-code
|
|
144
|
+
splunkctl state pull --dir config/ # snapshot live state
|
|
145
|
+
splunkctl state diff --dir config/ # structured drift report
|
|
146
|
+
splunkctl state push --dir config/ --report r.json --yes # deploy + evidence
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Commands
|
|
150
|
+
|
|
151
|
+
| Group | Description |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `doctor` | Connection, auth, health, and permissions check |
|
|
154
|
+
| `config` | Setup, profiles (dev/UAT/prod), test connectivity |
|
|
155
|
+
| `info` | Server info (version, OS, license) |
|
|
156
|
+
| `search` | Run, export, oneshot, upload, job management |
|
|
157
|
+
| `rules` | Detection rules — CRUD, import/export (YAML), alert-action flags |
|
|
158
|
+
| `alerts` | Fired alerts, alert actions, suppression |
|
|
159
|
+
| `dashboards` | Dashboard CRUD (XML/JSON) |
|
|
160
|
+
| `indexes` | Index management |
|
|
161
|
+
| `inputs` | Data inputs (monitor, tcp, udp, script, http) |
|
|
162
|
+
| `lookups` | Lookup tables, definitions, automatic lookups |
|
|
163
|
+
| `hec` | HEC token management |
|
|
164
|
+
| `parsers` | Source types, field extractions, import/export |
|
|
165
|
+
| `apps` | App install (.spl/.tar.gz), uninstall, update |
|
|
166
|
+
| `users` | User and role management |
|
|
167
|
+
| `server` | Messages, license, KV store, cluster/SHC/deployment health |
|
|
168
|
+
| `es` | ES notable-event triage (feature-detected) |
|
|
169
|
+
| `audit` | Change audit + RBAC attestation |
|
|
170
|
+
| `kvstore` | KV store collection + document CRUD |
|
|
171
|
+
| `conf` | Generic conf file/stanza editor (any .conf) |
|
|
172
|
+
| `macros` | Search macros — list, get, set |
|
|
173
|
+
| `eventtypes` | Event types — list, get |
|
|
174
|
+
| `tags` | Tags — list, get |
|
|
175
|
+
| `datamodels` | Data model definitions + acceleration health |
|
|
176
|
+
| `state` | Config-as-code pull/diff/push with change-evidence reports |
|
|
177
|
+
| `commands` | Machine-readable command tree (JSON) |
|
|
178
|
+
| `mcp` | Built-in MCP server for AI agent integration |
|
|
179
|
+
|
|
180
|
+
## Global flags
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
--json Force JSON output
|
|
184
|
+
--format FMT Output format: table, json, csv, jsonl
|
|
185
|
+
--fields f1,f2 Project specific fields
|
|
186
|
+
--out FILE Write output to file
|
|
187
|
+
--yes / -y Apply mutations (skip dry-run preview)
|
|
188
|
+
--timeout N Request timeout in seconds (default 30)
|
|
189
|
+
--config FILE Config file path
|
|
190
|
+
--profile NAME Named profile (dev/UAT/prod)
|
|
191
|
+
--debug HTTP request/response logging
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Dry-run by default
|
|
195
|
+
|
|
196
|
+
All write operations preview what would change. Pass `--yes` to apply.
|
|
197
|
+
Every preview and confirmation includes the target profile and host so an
|
|
198
|
+
agent never mistakes UAT for prod.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
splunkctl rules delete 'My Rule'
|
|
202
|
+
# [DRY RUN] Delete saved search 'My Rule' (profile: uat @ uat.splunk.internal:8089)
|
|
203
|
+
# Pass --yes to apply.
|
|
204
|
+
|
|
205
|
+
splunkctl rules delete 'My Rule' --yes
|
|
206
|
+
# Applying: Delete saved search 'My Rule' (profile: uat @ uat.splunk.internal:8089)
|
|
207
|
+
# Deleted saved search 'My Rule'.
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Structured errors
|
|
211
|
+
|
|
212
|
+
Under `--json` or piped output, errors emit a single-line JSON envelope on
|
|
213
|
+
stderr with a typed `kind` for programmatic branching:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{"error": {"kind": "not_found", "http_status": 404, "message": "..."}}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Kinds: `auth`, `permission`, `not_found`, `conflict`, `http`, `connection`,
|
|
220
|
+
`timeout`, `error` (fallback).
|
|
221
|
+
|
|
222
|
+
## SDK fork
|
|
223
|
+
|
|
224
|
+
splunkctl depends on a
|
|
225
|
+
[fork of splunk-sdk-python](https://github.com/dannyota/splunk-sdk-python/tree/splunkctl)
|
|
226
|
+
that adds entity classes missing from the upstream SDK:
|
|
227
|
+
|
|
228
|
+
| Entity | Service property | Purpose |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| `Dashboard` | `service.dashboards` | Dashboard CRUD |
|
|
231
|
+
| `LookupTableFile` | `service.lookup_table_files` | Lookup table metadata + download |
|
|
232
|
+
| `HECToken` | `service.hec_tokens` | HEC token management |
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
pip install git+https://github.com/dannyota/splunk-sdk-python@splunkctl
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Agent integration (MCP)
|
|
239
|
+
|
|
240
|
+
splunkctl ships with a built-in
|
|
241
|
+
[MCP](https://modelcontextprotocol.io) server for AI agent integration:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
splunkctl mcp install # register in .mcp.json
|
|
245
|
+
splunkctl mcp serve # start stdio MCP server
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The MCP server auto-generates 129 typed tools from the Click command tree
|
|
249
|
+
with progressive discovery — agents start with 5 meta-tools (`help`,
|
|
250
|
+
`usage`, `focus`, `unfocus`, `run`) and dynamically load typed schemas per
|
|
251
|
+
command group. 22 guide resources are served as `guide://` URIs. Mutations
|
|
252
|
+
are guarded: `yes=true` to apply (dry-run by default).
|
|
253
|
+
|
|
254
|
+
## License
|
|
255
|
+
|
|
256
|
+
Apache-2.0
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<a href="https://splunk.danny.vn"><img src="docs/assets/banner.svg" alt="splunkctl — operate Splunk Enterprise as code" width="600"></a>
|
|
4
|
+
|
|
5
|
+
# splunkctl
|
|
6
|
+
|
|
7
|
+
**Operate Splunk Enterprise as code — for SOC teams, detection engineers, and AI agents.**
|
|
8
|
+
|
|
9
|
+
[Docs](https://splunk.danny.vn) · [Catalog](docs/design/catalog.md) · [Releases](https://github.com/dannyota/splunkctl/releases)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
A Python CLI that queries, inspects, and manages a **remote** Splunk
|
|
16
|
+
Enterprise instance over the REST API. Built on the
|
|
17
|
+
[splunk-sdk-python](https://github.com/dannyota/splunk-sdk-python/tree/splunkctl)
|
|
18
|
+
fork with [Click](https://click.palletsprojects.com/). The core loop is
|
|
19
|
+
**pull live state → review the diff → push it back** — one `state` engine
|
|
20
|
+
covering rules, parsers, macros, lookups, and dashboards, with a
|
|
21
|
+
change-evidence report artifact for bank change tickets. It's built for
|
|
22
|
+
humans and LLM agents alike: deterministic flags, `--json` everywhere,
|
|
23
|
+
structured error envelopes, and a built-in MCP server
|
|
24
|
+
(`splunkctl mcp serve`) with progressive tool discovery.
|
|
25
|
+
|
|
26
|
+
> **Every mutation is dry-run by default.** Nothing changes until you pass
|
|
27
|
+
> `--yes`. Always preview, read it, then apply.
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
- **Config as code** — `state pull` → edit → `state diff` → `state push`
|
|
32
|
+
across rules, parsers, macros, lookups, and dashboards (diff-only). Push
|
|
33
|
+
writes a before→after JSON report artifact usable as change-ticket
|
|
34
|
+
evidence. Push never deletes.
|
|
35
|
+
- **Detection engineering** — rules CRUD + YAML import/export, macros,
|
|
36
|
+
eventtypes, tags, data model acceleration health, lookup definitions +
|
|
37
|
+
automatic lookups (transforms.conf/props.conf wiring), and first-class
|
|
38
|
+
`--email-to`/`--webhook-url` alert-action flags.
|
|
39
|
+
- **ES incident review** — `es notables list/get/update` for the SOC
|
|
40
|
+
triage loop (status, owner, urgency, disposition, comment via
|
|
41
|
+
`notable_update`); feature-detected on Enterprise Security.
|
|
42
|
+
- **Compliance & audit** — `audit changes` normalizes both `_audit` event
|
|
43
|
+
shapes into one schema; `audit rbac` produces a users × roles ×
|
|
44
|
+
capabilities attestation view for access recertification.
|
|
45
|
+
- **KV store** — collection + document CRUD, JSONL import/export with
|
|
46
|
+
500-doc batch chunking, query with server-side filtering.
|
|
47
|
+
- **Topology health** — `server cluster/shcluster/deployment` reads
|
|
48
|
+
distinguish "no threat" from "an indexer is down" in clustered
|
|
49
|
+
deployments.
|
|
50
|
+
- **Agent reliability** — structured JSON error envelope with typed
|
|
51
|
+
taxonomy (`auth`/`permission`/`not_found`/`timeout`/...), uniform
|
|
52
|
+
`--limit`/`--offset`/`--filter` on every list surface, multi-instance
|
|
53
|
+
profiles with a bank-safety guard banner
|
|
54
|
+
(`(profile: uat @ host:port)`).
|
|
55
|
+
- **Built for agents** — built-in MCP server with 129 auto-generated tools,
|
|
56
|
+
progressive discovery (5 meta-tools + focus/unfocus), 22 guide resources,
|
|
57
|
+
guard markers on every mutation, dual output (TTY = table, pipe = JSON).
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install splunkctl
|
|
63
|
+
pip install git+https://github.com/dannyota/splunk-sdk-python@splunkctl
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requires Python 3.13+. The second line installs the
|
|
67
|
+
[forked SDK](https://github.com/dannyota/splunk-sdk-python/tree/splunkctl)
|
|
68
|
+
which adds dashboard, lookup, and HEC token entity classes. Without it,
|
|
69
|
+
core commands (search, rules, alerts, indexes, inputs, apps, users) still
|
|
70
|
+
work.
|
|
71
|
+
|
|
72
|
+
### Development
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/dannyota/splunkctl
|
|
76
|
+
cd splunkctl
|
|
77
|
+
pip install -e '.[dev]'
|
|
78
|
+
splunkctl --version
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quickstart
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
splunkctl config init # interactive setup
|
|
85
|
+
splunkctl doctor # check connection, auth, permissions
|
|
86
|
+
splunkctl search run 'index=main | head 10' # run a search
|
|
87
|
+
splunkctl rules list # list detection rules
|
|
88
|
+
splunkctl commands --json # discover every verb
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## CLI usage
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Read
|
|
95
|
+
splunkctl rules list --app Splunk_Security_Essentials --json
|
|
96
|
+
splunkctl alerts list --json
|
|
97
|
+
splunkctl datamodels acceleration
|
|
98
|
+
splunkctl audit rbac --format csv --out rbac.csv
|
|
99
|
+
|
|
100
|
+
# Mutate (dry-run first, --yes to apply)
|
|
101
|
+
splunkctl rules disable 'My Rule' # preview
|
|
102
|
+
splunkctl rules disable 'My Rule' --yes # apply
|
|
103
|
+
splunkctl es notables update <id> --status closed --owner analyst --yes
|
|
104
|
+
|
|
105
|
+
# Config-as-code
|
|
106
|
+
splunkctl state pull --dir config/ # snapshot live state
|
|
107
|
+
splunkctl state diff --dir config/ # structured drift report
|
|
108
|
+
splunkctl state push --dir config/ --report r.json --yes # deploy + evidence
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
| Group | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `doctor` | Connection, auth, health, and permissions check |
|
|
116
|
+
| `config` | Setup, profiles (dev/UAT/prod), test connectivity |
|
|
117
|
+
| `info` | Server info (version, OS, license) |
|
|
118
|
+
| `search` | Run, export, oneshot, upload, job management |
|
|
119
|
+
| `rules` | Detection rules — CRUD, import/export (YAML), alert-action flags |
|
|
120
|
+
| `alerts` | Fired alerts, alert actions, suppression |
|
|
121
|
+
| `dashboards` | Dashboard CRUD (XML/JSON) |
|
|
122
|
+
| `indexes` | Index management |
|
|
123
|
+
| `inputs` | Data inputs (monitor, tcp, udp, script, http) |
|
|
124
|
+
| `lookups` | Lookup tables, definitions, automatic lookups |
|
|
125
|
+
| `hec` | HEC token management |
|
|
126
|
+
| `parsers` | Source types, field extractions, import/export |
|
|
127
|
+
| `apps` | App install (.spl/.tar.gz), uninstall, update |
|
|
128
|
+
| `users` | User and role management |
|
|
129
|
+
| `server` | Messages, license, KV store, cluster/SHC/deployment health |
|
|
130
|
+
| `es` | ES notable-event triage (feature-detected) |
|
|
131
|
+
| `audit` | Change audit + RBAC attestation |
|
|
132
|
+
| `kvstore` | KV store collection + document CRUD |
|
|
133
|
+
| `conf` | Generic conf file/stanza editor (any .conf) |
|
|
134
|
+
| `macros` | Search macros — list, get, set |
|
|
135
|
+
| `eventtypes` | Event types — list, get |
|
|
136
|
+
| `tags` | Tags — list, get |
|
|
137
|
+
| `datamodels` | Data model definitions + acceleration health |
|
|
138
|
+
| `state` | Config-as-code pull/diff/push with change-evidence reports |
|
|
139
|
+
| `commands` | Machine-readable command tree (JSON) |
|
|
140
|
+
| `mcp` | Built-in MCP server for AI agent integration |
|
|
141
|
+
|
|
142
|
+
## Global flags
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
--json Force JSON output
|
|
146
|
+
--format FMT Output format: table, json, csv, jsonl
|
|
147
|
+
--fields f1,f2 Project specific fields
|
|
148
|
+
--out FILE Write output to file
|
|
149
|
+
--yes / -y Apply mutations (skip dry-run preview)
|
|
150
|
+
--timeout N Request timeout in seconds (default 30)
|
|
151
|
+
--config FILE Config file path
|
|
152
|
+
--profile NAME Named profile (dev/UAT/prod)
|
|
153
|
+
--debug HTTP request/response logging
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Dry-run by default
|
|
157
|
+
|
|
158
|
+
All write operations preview what would change. Pass `--yes` to apply.
|
|
159
|
+
Every preview and confirmation includes the target profile and host so an
|
|
160
|
+
agent never mistakes UAT for prod.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
splunkctl rules delete 'My Rule'
|
|
164
|
+
# [DRY RUN] Delete saved search 'My Rule' (profile: uat @ uat.splunk.internal:8089)
|
|
165
|
+
# Pass --yes to apply.
|
|
166
|
+
|
|
167
|
+
splunkctl rules delete 'My Rule' --yes
|
|
168
|
+
# Applying: Delete saved search 'My Rule' (profile: uat @ uat.splunk.internal:8089)
|
|
169
|
+
# Deleted saved search 'My Rule'.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Structured errors
|
|
173
|
+
|
|
174
|
+
Under `--json` or piped output, errors emit a single-line JSON envelope on
|
|
175
|
+
stderr with a typed `kind` for programmatic branching:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{"error": {"kind": "not_found", "http_status": 404, "message": "..."}}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Kinds: `auth`, `permission`, `not_found`, `conflict`, `http`, `connection`,
|
|
182
|
+
`timeout`, `error` (fallback).
|
|
183
|
+
|
|
184
|
+
## SDK fork
|
|
185
|
+
|
|
186
|
+
splunkctl depends on a
|
|
187
|
+
[fork of splunk-sdk-python](https://github.com/dannyota/splunk-sdk-python/tree/splunkctl)
|
|
188
|
+
that adds entity classes missing from the upstream SDK:
|
|
189
|
+
|
|
190
|
+
| Entity | Service property | Purpose |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `Dashboard` | `service.dashboards` | Dashboard CRUD |
|
|
193
|
+
| `LookupTableFile` | `service.lookup_table_files` | Lookup table metadata + download |
|
|
194
|
+
| `HECToken` | `service.hec_tokens` | HEC token management |
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
pip install git+https://github.com/dannyota/splunk-sdk-python@splunkctl
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Agent integration (MCP)
|
|
201
|
+
|
|
202
|
+
splunkctl ships with a built-in
|
|
203
|
+
[MCP](https://modelcontextprotocol.io) server for AI agent integration:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
splunkctl mcp install # register in .mcp.json
|
|
207
|
+
splunkctl mcp serve # start stdio MCP server
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The MCP server auto-generates 129 typed tools from the Click command tree
|
|
211
|
+
with progressive discovery — agents start with 5 meta-tools (`help`,
|
|
212
|
+
`usage`, `focus`, `unfocus`, `run`) and dynamically load typed schemas per
|
|
213
|
+
command group. 22 guide resources are served as `guide://` URIs. Mutations
|
|
214
|
+
are guarded: `yes=true` to apply (dry-run by default).
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
Apache-2.0
|
|
@@ -29,6 +29,7 @@ dependencies = [
|
|
|
29
29
|
"tabulate>=0.9",
|
|
30
30
|
"requests>=2.32",
|
|
31
31
|
"defusedxml>=0.7",
|
|
32
|
+
"mcp>=1.23",
|
|
32
33
|
]
|
|
33
34
|
|
|
34
35
|
[project.optional-dependencies]
|
|
@@ -51,13 +52,13 @@ Repository = "https://github.com/dannyota/splunkctl"
|
|
|
51
52
|
Issues = "https://github.com/dannyota/splunkctl/issues"
|
|
52
53
|
|
|
53
54
|
[tool.setuptools]
|
|
54
|
-
packages = ["splunkctl", "splunkctl.commands", "splunkctl.
|
|
55
|
+
packages = ["splunkctl", "splunkctl.commands", "splunkctl.mcp"]
|
|
55
56
|
|
|
56
57
|
[tool.setuptools.dynamic]
|
|
57
58
|
version = { attr = "splunkctl.__version__" }
|
|
58
59
|
|
|
59
60
|
[tool.setuptools.package-data]
|
|
60
|
-
"splunkctl.
|
|
61
|
+
"splunkctl.mcp" = ["py.typed"]
|
|
61
62
|
|
|
62
63
|
# --- Ruff (lint + format) ---
|
|
63
64
|
|
|
@@ -102,6 +103,10 @@ warn_unused_configs = true
|
|
|
102
103
|
module = "splunklib.*"
|
|
103
104
|
ignore_missing_imports = true
|
|
104
105
|
|
|
106
|
+
[[tool.mypy.overrides]]
|
|
107
|
+
module = "mcp.*"
|
|
108
|
+
ignore_missing_imports = true
|
|
109
|
+
|
|
105
110
|
# --- pytest ---
|
|
106
111
|
|
|
107
112
|
[tool.pytest.ini_options]
|
|
@@ -354,6 +354,29 @@ _REQUIRED_ACTION_FIELDS: dict[str, str] = {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
|
|
357
|
+
def app_scope(app: str | None) -> dict[str, str]:
|
|
358
|
+
"""Build the ``app``/``owner`` kwargs for an app-scoped list call.
|
|
359
|
+
|
|
360
|
+
Unscoped by default (the connection's current namespace); ``owner="-"``
|
|
361
|
+
when an app is given so app-private stanzas owned by other users aren't
|
|
362
|
+
silently excluded.
|
|
363
|
+
"""
|
|
364
|
+
return {} if app is None else {"app": app, "owner": "-"}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def trunc(value: str, limit: int = 60) -> str:
|
|
368
|
+
"""Shorten a long text field for table display.
|
|
369
|
+
|
|
370
|
+
Never emits a bare ellipsis — a truncated value always says how many
|
|
371
|
+
characters were hidden, e.g. ``foo… [+57 chars]``.
|
|
372
|
+
"""
|
|
373
|
+
if len(value) <= limit:
|
|
374
|
+
return value
|
|
375
|
+
kept = value[: limit - 1]
|
|
376
|
+
hidden = len(value) - len(kept)
|
|
377
|
+
return f"{kept}… [+{hidden} chars]"
|
|
378
|
+
|
|
379
|
+
|
|
357
380
|
def warn_missing_action_fields(
|
|
358
381
|
actions: str,
|
|
359
382
|
kwargs: dict[str, Any],
|
|
@@ -15,7 +15,7 @@ import click
|
|
|
15
15
|
from splunkctl import guard, output
|
|
16
16
|
from splunkctl.client import get_client
|
|
17
17
|
from splunkctl.commands import conf_ops
|
|
18
|
-
from splunkctl.commands.common import fetch_page, list_options, parse_set
|
|
18
|
+
from splunkctl.commands.common import app_scope, fetch_page, list_options, parse_set
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _not_found_message(exc: KeyError, file: str, stanza: str) -> str:
|
|
@@ -35,18 +35,6 @@ def _not_found_message(exc: KeyError, file: str, stanza: str) -> str:
|
|
|
35
35
|
return f"Stanza '{stanza}' not found in {file}.conf."
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def _app_scope(app: str | None) -> dict[str, str]:
|
|
39
|
-
"""Build the ``app``/``owner`` kwargs for an app-scoped list call.
|
|
40
|
-
|
|
41
|
-
Mirrors the ``rules``/``dashboards`` scoping convention: unscoped by
|
|
42
|
-
default (matches ``parsers``' existing default, i.e. whatever the
|
|
43
|
-
connection's current namespace resolves to), ``owner="-"`` when an
|
|
44
|
-
app is given so app-private stanzas owned by other users aren't
|
|
45
|
-
silently excluded.
|
|
46
|
-
"""
|
|
47
|
-
return {} if app is None else {"app": app, "owner": "-"}
|
|
48
|
-
|
|
49
|
-
|
|
50
38
|
@click.group("conf")
|
|
51
39
|
def conf_group() -> None:
|
|
52
40
|
"""Generic conf file/stanza editor (any conf, including sensitive ones)."""
|
|
@@ -70,7 +58,7 @@ def files(
|
|
|
70
58
|
) -> None:
|
|
71
59
|
"""List conf files known to the server."""
|
|
72
60
|
client = get_client(ctx)
|
|
73
|
-
scope =
|
|
61
|
+
scope = app_scope(app)
|
|
74
62
|
items = fetch_page(
|
|
75
63
|
lambda **pg: client.service.confs.list(**scope, **pg),
|
|
76
64
|
limit=limit,
|
|
@@ -102,7 +90,7 @@ def list_stanzas(
|
|
|
102
90
|
) -> None:
|
|
103
91
|
"""List stanzas in a conf file."""
|
|
104
92
|
client = get_client(ctx)
|
|
105
|
-
scope =
|
|
93
|
+
scope = app_scope(app)
|
|
106
94
|
items = fetch_page(
|
|
107
95
|
lambda **pg: client.service.confs[file].list(**scope, **pg),
|
|
108
96
|
limit=limit,
|
|
@@ -123,13 +111,21 @@ def list_stanzas(
|
|
|
123
111
|
@conf_group.command("get")
|
|
124
112
|
@click.argument("file")
|
|
125
113
|
@click.argument("stanza")
|
|
114
|
+
@click.option(
|
|
115
|
+
"--app",
|
|
116
|
+
default=None,
|
|
117
|
+
help="App to resolve the stanza in, when the same stanza exists in "
|
|
118
|
+
"more than one app (default: current namespace).",
|
|
119
|
+
)
|
|
126
120
|
@click.option("--key", default=None, help="Show only this key's value.")
|
|
127
121
|
@click.pass_context
|
|
128
|
-
def get(
|
|
122
|
+
def get(
|
|
123
|
+
ctx: click.Context, file: str, stanza: str, app: str | None, key: str | None
|
|
124
|
+
) -> None:
|
|
129
125
|
"""Show a conf stanza's keys, or one key's value with --key."""
|
|
130
126
|
client = get_client(ctx)
|
|
131
127
|
try:
|
|
132
|
-
entity = conf_ops.get_stanza(client, file, stanza)
|
|
128
|
+
entity = conf_ops.get_stanza(client, file, stanza, app=app)
|
|
133
129
|
except KeyError as exc:
|
|
134
130
|
output.error(_not_found_message(exc, file, stanza), kind="not_found")
|
|
135
131
|
ctx.exit(1)
|