qualys-cli 0.1.1__py3-none-any.whl
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.
- qualys_cli/__init__.py +1 -0
- qualys_cli/__main__.py +8 -0
- qualys_cli/audit.py +616 -0
- qualys_cli/auth.py +187 -0
- qualys_cli/cache.py +123 -0
- qualys_cli/cli.py +1168 -0
- qualys_cli/client.py +1043 -0
- qualys_cli/commands/__init__.py +0 -0
- qualys_cli/commands/asset.py +183 -0
- qualys_cli/commands/ca.py +403 -0
- qualys_cli/commands/cs.py +410 -0
- qualys_cli/commands/csam.py +752 -0
- qualys_cli/commands/etm.py +170 -0
- qualys_cli/commands/pc.py +255 -0
- qualys_cli/commands/pm.py +412 -0
- qualys_cli/commands/scanauth.py +291 -0
- qualys_cli/commands/sub.py +163 -0
- qualys_cli/commands/tc.py +539 -0
- qualys_cli/commands/user.py +104 -0
- qualys_cli/commands/vm.py +562 -0
- qualys_cli/commands/was.py +1278 -0
- qualys_cli/config.py +331 -0
- qualys_cli/extras.py +702 -0
- qualys_cli/flair.py +202 -0
- qualys_cli/formatters.py +896 -0
- qualys_cli/history.py +133 -0
- qualys_cli/http_server.py +126 -0
- qualys_cli/mcp_server.py +341 -0
- qualys_cli/metrics.py +106 -0
- qualys_cli/platforms.py +67 -0
- qualys_cli/queries.py +137 -0
- qualys_cli-0.1.1.data/data/share/qualys-cli/docs/usage.html +1230 -0
- qualys_cli-0.1.1.dist-info/METADATA +319 -0
- qualys_cli-0.1.1.dist-info/RECORD +37 -0
- qualys_cli-0.1.1.dist-info/WHEEL +4 -0
- qualys_cli-0.1.1.dist-info/entry_points.txt +2 -0
- qualys_cli-0.1.1.dist-info/licenses/LICENSE +21 -0
qualys_cli/flair.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Status messages for the interactive spinner.
|
|
3
|
+
|
|
4
|
+
We cycle a small playful banner of Qualys / cybersecurity-themed phrases under
|
|
5
|
+
the spinner while a request is in flight. Two reasons:
|
|
6
|
+
|
|
7
|
+
1. Long Qualys API calls (large host-list fetches, scan launches, exports)
|
|
8
|
+
can run for tens of seconds. A static "GET /api/2.0/fo/asset/host/" label
|
|
9
|
+
feels frozen; a rotating phrase is reassuring without being noisy.
|
|
10
|
+
2. The phrases are drawn from the *actual* Qualys offering — VMDR, TotalCloud,
|
|
11
|
+
Patch Management, Container Security, EASM, TruRisk, etc. — so each one
|
|
12
|
+
reinforces what the call is doing. The category is picked from the URL
|
|
13
|
+
path so the messages line up with the API being called.
|
|
14
|
+
|
|
15
|
+
Cycling cadence: ~700 ms. Slow enough to read, fast enough to feel alive.
|
|
16
|
+
|
|
17
|
+
Pure functions only — no I/O — so this module is trivially testable.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import random
|
|
23
|
+
from collections.abc import Sequence
|
|
24
|
+
|
|
25
|
+
# Each entry is a short verb-led phrase. Keep them under ~40 chars so the
|
|
26
|
+
# spinner doesn't wrap on narrow terminals. Trailing ellipsis is added by
|
|
27
|
+
# the renderer, not here.
|
|
28
|
+
|
|
29
|
+
_VM = [
|
|
30
|
+
"Scanning for vulnerabilities",
|
|
31
|
+
"Crunching QIDs",
|
|
32
|
+
"Detecting CVEs in the wild",
|
|
33
|
+
"Probing the attack surface",
|
|
34
|
+
"Hunting zero-days",
|
|
35
|
+
"Correlating vuln signatures",
|
|
36
|
+
"Tracing exploit chains",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
_PC = [
|
|
40
|
+
"Auditing compliance controls",
|
|
41
|
+
"Mapping policy posture",
|
|
42
|
+
"Evaluating CIS benchmarks",
|
|
43
|
+
"Checking regulatory drift",
|
|
44
|
+
"Reconciling control gates",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
_WAS = [
|
|
48
|
+
"Crawling web app surface",
|
|
49
|
+
"Fuzzing OWASP Top 10",
|
|
50
|
+
"Inspecting auth records",
|
|
51
|
+
"Hunting injection points",
|
|
52
|
+
"Tracing app-layer flaws",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
_TC = [
|
|
56
|
+
"Scanning cloud posture",
|
|
57
|
+
"Validating IAM blast radius",
|
|
58
|
+
"Mapping CSPM controls",
|
|
59
|
+
"Auditing misconfigurations",
|
|
60
|
+
"Tracing cloud-native risk",
|
|
61
|
+
"Inspecting Terraform plans",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
_CA = [
|
|
65
|
+
"Pinging cloud agents",
|
|
66
|
+
"Refreshing host inventory",
|
|
67
|
+
"Rotating activation keys",
|
|
68
|
+
"Reconciling agent telemetry",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
_CS = [
|
|
72
|
+
"Inspecting container images",
|
|
73
|
+
"Scanning Kubernetes layers",
|
|
74
|
+
"Tracing image provenance",
|
|
75
|
+
"Hunting in-cluster exposure",
|
|
76
|
+
"Auditing registry posture",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
_CSAM = [
|
|
80
|
+
"Inventorying cyber assets",
|
|
81
|
+
"Mapping EASM exposures",
|
|
82
|
+
"Discovering shadow IT",
|
|
83
|
+
"Resolving asset criticality",
|
|
84
|
+
"Painting the attack surface",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
_PM = [
|
|
88
|
+
"Staging patch deployment",
|
|
89
|
+
"Building remediation job",
|
|
90
|
+
"Rolling out fixes",
|
|
91
|
+
"Verifying patch readiness",
|
|
92
|
+
"Sequencing rollout windows",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
_ETM = [
|
|
96
|
+
"Computing TruRisk score",
|
|
97
|
+
"Bundling mitigation plan",
|
|
98
|
+
"Scoring exposure findings",
|
|
99
|
+
"Prioritising risk in flight",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
_ASSET = [
|
|
103
|
+
"Reconciling asset graph",
|
|
104
|
+
"Tagging hosts",
|
|
105
|
+
"Enumerating networks",
|
|
106
|
+
"Resolving asset criticality",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
_USER = [
|
|
110
|
+
"Enrolling subscription users",
|
|
111
|
+
"Verifying role assignments",
|
|
112
|
+
"Reviewing user privileges",
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
_SUB = [
|
|
116
|
+
"Inspecting subscription posture",
|
|
117
|
+
"Loading entitlement matrix",
|
|
118
|
+
"Reviewing license footprint",
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
_SCANAUTH = [
|
|
122
|
+
"Locking down auth records",
|
|
123
|
+
"Verifying vault handshakes",
|
|
124
|
+
"Rotating credentials securely",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
_AUTH = [
|
|
128
|
+
"Establishing secure channel",
|
|
129
|
+
"Forging session token",
|
|
130
|
+
"Verifying JWT signature",
|
|
131
|
+
"Authenticating with Qualys",
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
_GENERIC = [
|
|
135
|
+
"Talking to Qualys Cloud Platform",
|
|
136
|
+
"Negotiating TLS handshake",
|
|
137
|
+
"Streaming response securely",
|
|
138
|
+
"Decoding telemetry",
|
|
139
|
+
"Hashing payload integrity",
|
|
140
|
+
"Holding the line on egress",
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Match longest URL fragments first so e.g. "/qps/rest/3.0/" → WAS, not generic.
|
|
145
|
+
_PATH_RULES: list[tuple[str, list[str]]] = [
|
|
146
|
+
("/qps/rest/3.0/", _WAS),
|
|
147
|
+
("/api/2.0/fo/compliance", _PC),
|
|
148
|
+
("/api/2.0/fo/policy", _PC),
|
|
149
|
+
("/api/2.0/fo/scan/compliance", _PC),
|
|
150
|
+
("/api/2.0/fo/asset/", _ASSET),
|
|
151
|
+
("/api/2.0/fo/scan", _VM),
|
|
152
|
+
("/api/2.0/fo/knowledge_base", _VM),
|
|
153
|
+
("/api/2.0/fo/auth/", _SCANAUTH),
|
|
154
|
+
("/api/2.0/fo/vault", _SCANAUTH),
|
|
155
|
+
("/api/2.0/fo/user_prefs", _SUB),
|
|
156
|
+
("/api/2.0/fo/subscription", _SUB),
|
|
157
|
+
("/api/2.0/fo/user", _USER),
|
|
158
|
+
("/qps/rest/2.0/", _CA),
|
|
159
|
+
("/cloudview-api/", _TC),
|
|
160
|
+
("/cloudview/", _TC),
|
|
161
|
+
("/cdr-api/", _TC),
|
|
162
|
+
("/csapi/", _CS),
|
|
163
|
+
("/cs-api/", _CS),
|
|
164
|
+
("/csam/", _CSAM),
|
|
165
|
+
("/easm/", _CSAM),
|
|
166
|
+
("/am/v2/", _CSAM),
|
|
167
|
+
("/rest/2.0/search/am", _CSAM),
|
|
168
|
+
("/caui/", _CA),
|
|
169
|
+
("/pm/", _PM),
|
|
170
|
+
("/pcrs/", _PM),
|
|
171
|
+
("/etm/", _ETM),
|
|
172
|
+
("/auth", _AUTH),
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def messages_for_path(path: str) -> Sequence[str]:
|
|
177
|
+
"""Pick the message bucket that matches *path*. Falls back to GENERIC."""
|
|
178
|
+
for prefix, bucket in _PATH_RULES:
|
|
179
|
+
if prefix in path:
|
|
180
|
+
return bucket
|
|
181
|
+
return _GENERIC
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def cycle_for_path(path: str) -> list[str]:
|
|
185
|
+
"""Return a shuffled cycle of messages for the spinner.
|
|
186
|
+
|
|
187
|
+
We mix the path-specific bucket with a couple of generic phrases so even
|
|
188
|
+
short calls feel a bit varied. The first message is always the most
|
|
189
|
+
on-topic — the one users see while the call still feels instant — and
|
|
190
|
+
the rest rotate after that.
|
|
191
|
+
"""
|
|
192
|
+
primary = list(messages_for_path(path))
|
|
193
|
+
rng = random.Random() # not seeded — tests don't assert on order
|
|
194
|
+
rng.shuffle(primary)
|
|
195
|
+
extras = rng.sample(_GENERIC, k=min(2, len(_GENERIC)))
|
|
196
|
+
out: list[str] = []
|
|
197
|
+
seen: set[str] = set()
|
|
198
|
+
for m in primary + extras:
|
|
199
|
+
if m not in seen:
|
|
200
|
+
out.append(m)
|
|
201
|
+
seen.add(m)
|
|
202
|
+
return out
|