prelude-cli-beta 1446__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.
- prelude_cli_beta/__init__.py +0 -0
- prelude_cli_beta/cli.py +52 -0
- prelude_cli_beta/templates/__init__.py +0 -0
- prelude_cli_beta/views/__init__.py +0 -0
- prelude_cli_beta/views/auth.py +56 -0
- prelude_cli_beta/views/build.py +488 -0
- prelude_cli_beta/views/configure.py +29 -0
- prelude_cli_beta/views/detect.py +438 -0
- prelude_cli_beta/views/generate.py +130 -0
- prelude_cli_beta/views/iam.py +368 -0
- prelude_cli_beta/views/jobs.py +50 -0
- prelude_cli_beta/views/partner.py +181 -0
- prelude_cli_beta/views/scm.py +881 -0
- prelude_cli_beta/views/shared.py +37 -0
- prelude_cli_beta-1446.dist-info/METADATA +38 -0
- prelude_cli_beta-1446.dist-info/RECORD +20 -0
- prelude_cli_beta-1446.dist-info/WHEEL +5 -0
- prelude_cli_beta-1446.dist-info/entry_points.txt +3 -0
- prelude_cli_beta-1446.dist-info/licenses/LICENSE +9 -0
- prelude_cli_beta-1446.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import json
|
|
3
|
+
import requests
|
|
4
|
+
from time import sleep
|
|
5
|
+
|
|
6
|
+
from prelude_cli_beta.views.shared import Spinner, pretty_print
|
|
7
|
+
from prelude_sdk_beta.controllers.export_controller import ExportController
|
|
8
|
+
from prelude_sdk_beta.controllers.jobs_controller import JobsController
|
|
9
|
+
from prelude_sdk_beta.controllers.scm_controller import ScmController
|
|
10
|
+
from prelude_sdk_beta.models.codes import (
|
|
11
|
+
Control,
|
|
12
|
+
ControlCategory,
|
|
13
|
+
PartnerEvents,
|
|
14
|
+
PolicyType,
|
|
15
|
+
RunCode,
|
|
16
|
+
SCMCategory,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.group()
|
|
21
|
+
@click.pass_context
|
|
22
|
+
def scm(ctx):
|
|
23
|
+
"""SCM system commands"""
|
|
24
|
+
ctx.obj = ScmController(account=ctx.obj)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@scm.command("endpoints")
|
|
28
|
+
@click.option(
|
|
29
|
+
"--limit", default=100, help="maximum number of results to return", type=int
|
|
30
|
+
)
|
|
31
|
+
@click.option("--odata_filter", help="OData filter string", default=None)
|
|
32
|
+
@click.option("--odata_orderby", help="OData orderby string", default=None)
|
|
33
|
+
@click.pass_obj
|
|
34
|
+
@pretty_print
|
|
35
|
+
def endpoints(controller, limit, odata_filter, odata_orderby):
|
|
36
|
+
"""List endpoints with SCM data"""
|
|
37
|
+
with Spinner(description="Fetching endpoints from partner"):
|
|
38
|
+
return controller.endpoints(
|
|
39
|
+
filter=odata_filter, orderby=odata_orderby, top=limit
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@scm.command("inboxes")
|
|
44
|
+
@click.option(
|
|
45
|
+
"--limit", default=100, help="maximum number of results to return", type=int
|
|
46
|
+
)
|
|
47
|
+
@click.option("--odata_filter", help="OData filter string", default=None)
|
|
48
|
+
@click.option("--odata_orderby", help="OData orderby string", default=None)
|
|
49
|
+
@click.pass_obj
|
|
50
|
+
@pretty_print
|
|
51
|
+
def inboxes(controller, limit, odata_filter, odata_orderby):
|
|
52
|
+
"""List inboxes with SCM data"""
|
|
53
|
+
with Spinner(description="Fetching inboxes from partner"):
|
|
54
|
+
return controller.inboxes(filter=odata_filter, orderby=odata_orderby, top=limit)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@scm.command("network_devices")
|
|
58
|
+
@click.option(
|
|
59
|
+
"--limit", default=100, help="maximum number of results to return", type=int
|
|
60
|
+
)
|
|
61
|
+
@click.option("--odata_filter", help="OData filter string", default=None)
|
|
62
|
+
@click.option("--odata_orderby", help="OData orderby string", default=None)
|
|
63
|
+
@click.pass_obj
|
|
64
|
+
@pretty_print
|
|
65
|
+
def network_devices(controller, limit, odata_filter, odata_orderby):
|
|
66
|
+
"""List network devices with SCM data"""
|
|
67
|
+
with Spinner(description="Fetching network devices from partner"):
|
|
68
|
+
return controller.network_devices(
|
|
69
|
+
filter=odata_filter, orderby=odata_orderby, top=limit
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@scm.command("users")
|
|
74
|
+
@click.option(
|
|
75
|
+
"--limit", default=100, help="maximum number of results to return", type=int
|
|
76
|
+
)
|
|
77
|
+
@click.option("--odata_filter", help="OData filter string", default=None)
|
|
78
|
+
@click.option("--odata_orderby", help="OData orderby string", default=None)
|
|
79
|
+
@click.pass_obj
|
|
80
|
+
@pretty_print
|
|
81
|
+
def users(controller, limit, odata_filter, odata_orderby):
|
|
82
|
+
"""List users with SCM data"""
|
|
83
|
+
with Spinner(description="Fetching users from partner"):
|
|
84
|
+
return controller.users(filter=odata_filter, orderby=odata_orderby, top=limit)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@scm.command("technique-summary")
|
|
88
|
+
@click.option(
|
|
89
|
+
"-q",
|
|
90
|
+
"--techniques",
|
|
91
|
+
help="comma-separated list of techniques to filter by",
|
|
92
|
+
type=str,
|
|
93
|
+
required=True,
|
|
94
|
+
)
|
|
95
|
+
@click.pass_obj
|
|
96
|
+
@pretty_print
|
|
97
|
+
def technique_summary(controller, techniques):
|
|
98
|
+
"""Get policy summary per technique"""
|
|
99
|
+
with Spinner(description="Getting policy summary by technique"):
|
|
100
|
+
return controller.technique_summary(techniques=techniques)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@scm.command("evaluation-summary")
|
|
104
|
+
@click.option(
|
|
105
|
+
"--endpoint_odata_filter", help="OData filter string for endpoints", default=None
|
|
106
|
+
)
|
|
107
|
+
@click.option(
|
|
108
|
+
"--inbox_odata_filter", help="OData filter string for inboxes", default=None
|
|
109
|
+
)
|
|
110
|
+
@click.option("--user_odata_filter", help="OData filter string for users", default=None)
|
|
111
|
+
@click.option(
|
|
112
|
+
"-q",
|
|
113
|
+
"--techniques",
|
|
114
|
+
help="comma-separated list of techniques to filter by",
|
|
115
|
+
type=str,
|
|
116
|
+
default=None,
|
|
117
|
+
)
|
|
118
|
+
@click.pass_obj
|
|
119
|
+
@pretty_print
|
|
120
|
+
def evaluation_summary(
|
|
121
|
+
controller, endpoint_odata_filter, inbox_odata_filter, user_odata_filter, techniques
|
|
122
|
+
):
|
|
123
|
+
"""Get policy evaluation summary for all partners"""
|
|
124
|
+
with Spinner(description="Getting policy evaluation summary"):
|
|
125
|
+
return controller.evaluation_summary(
|
|
126
|
+
endpoint_filter=endpoint_odata_filter,
|
|
127
|
+
inbox_filter=inbox_odata_filter,
|
|
128
|
+
user_filter=user_odata_filter,
|
|
129
|
+
techniques=techniques,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@scm.command("evaluation")
|
|
134
|
+
@click.argument(
|
|
135
|
+
"partner",
|
|
136
|
+
type=click.Choice(
|
|
137
|
+
[c.name for c in Control if c != Control.INVALID], case_sensitive=False
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
@click.option("--instance_id", required=True, help="instance ID of the partner")
|
|
141
|
+
@click.option("--odata_filter", help="OData filter string", default=None)
|
|
142
|
+
@click.option(
|
|
143
|
+
"-p",
|
|
144
|
+
"--policy_type",
|
|
145
|
+
help="Policy types to filter by",
|
|
146
|
+
multiple=True,
|
|
147
|
+
default=[],
|
|
148
|
+
type=click.Choice(
|
|
149
|
+
[p.name for p in PolicyType if p != PolicyType.INVALID], case_sensitive=False
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
@click.option(
|
|
153
|
+
"-q",
|
|
154
|
+
"--techniques",
|
|
155
|
+
help="comma-separated list of techniques to filter by",
|
|
156
|
+
type=str,
|
|
157
|
+
default=None,
|
|
158
|
+
)
|
|
159
|
+
@click.pass_obj
|
|
160
|
+
@pretty_print
|
|
161
|
+
def evaluation(controller, partner, instance_id, odata_filter, policy_type, techniques):
|
|
162
|
+
"""Get policy evaluation for given partner"""
|
|
163
|
+
with Spinner(description="Getting policy evaluation"):
|
|
164
|
+
return controller.evaluation(
|
|
165
|
+
partner=Control[partner],
|
|
166
|
+
instance_id=instance_id,
|
|
167
|
+
filter=odata_filter,
|
|
168
|
+
policy_types=",".join(policy_type),
|
|
169
|
+
techniques=techniques,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@scm.command("sync")
|
|
174
|
+
@click.argument(
|
|
175
|
+
"partner",
|
|
176
|
+
type=click.Choice(
|
|
177
|
+
[c.name for c in Control if c != Control.INVALID], case_sensitive=False
|
|
178
|
+
),
|
|
179
|
+
required=True,
|
|
180
|
+
)
|
|
181
|
+
@click.option("--instance_id", required=True, help="instance ID of the partner")
|
|
182
|
+
@click.pass_obj
|
|
183
|
+
@pretty_print
|
|
184
|
+
def sync(controller, partner, instance_id):
|
|
185
|
+
"""Update policy evaluation for given partner"""
|
|
186
|
+
with Spinner(description="Updating policy evaluation"):
|
|
187
|
+
job_id = controller.update_evaluation(
|
|
188
|
+
partner=Control[partner], instance_id=instance_id
|
|
189
|
+
)["job_id"]
|
|
190
|
+
jobs = JobsController(account=controller.account)
|
|
191
|
+
while (result := jobs.job_status(job_id))["end_time"] is None:
|
|
192
|
+
sleep(3)
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@scm.command("export")
|
|
197
|
+
@click.argument(
|
|
198
|
+
"type",
|
|
199
|
+
type=click.Choice(
|
|
200
|
+
[c.name for c in SCMCategory if c.value > 0], case_sensitive=False
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
@click.option(
|
|
204
|
+
"-o",
|
|
205
|
+
"--output_file",
|
|
206
|
+
help="csv filename to export to",
|
|
207
|
+
type=click.Path(writable=True),
|
|
208
|
+
required=True,
|
|
209
|
+
)
|
|
210
|
+
@click.option(
|
|
211
|
+
"--limit", default=None, help="maximum number of results to return", type=int
|
|
212
|
+
)
|
|
213
|
+
@click.option("--odata_filter", help="OData filter string", default=None)
|
|
214
|
+
@click.option("--odata_orderby", help="OData orderby string", default=None)
|
|
215
|
+
@click.pass_obj
|
|
216
|
+
@pretty_print
|
|
217
|
+
def export(controller, type, output_file, limit, odata_filter, odata_orderby):
|
|
218
|
+
"""Export SCM data"""
|
|
219
|
+
with Spinner(description="Exporting SCM data"):
|
|
220
|
+
export = ExportController(account=controller.account)
|
|
221
|
+
jobs = JobsController(account=controller.account)
|
|
222
|
+
job_id = export.export_scm(
|
|
223
|
+
export_type=SCMCategory[type],
|
|
224
|
+
filter=odata_filter,
|
|
225
|
+
orderby=odata_orderby,
|
|
226
|
+
top=limit,
|
|
227
|
+
)["job_id"]
|
|
228
|
+
while (result := jobs.job_status(job_id))["end_time"] is None:
|
|
229
|
+
sleep(3)
|
|
230
|
+
if result["successful"]:
|
|
231
|
+
data = requests.get(result["results"]["url"], timeout=10).content
|
|
232
|
+
with open(output_file, "wb") as f:
|
|
233
|
+
f.write(data)
|
|
234
|
+
return result, f"Exported data to {output_file}"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@click.group()
|
|
238
|
+
@click.pass_context
|
|
239
|
+
def group(ctx):
|
|
240
|
+
"""SCM group commands"""
|
|
241
|
+
ctx.obj = ScmController(account=ctx.obj.account)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
scm.add_command(group)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@group.command("list")
|
|
248
|
+
@click.option("--odata_filter", help="OData filter string", default=None)
|
|
249
|
+
@click.option("--odata_orderby", help="OData orderby string", default=None)
|
|
250
|
+
@click.pass_obj
|
|
251
|
+
@pretty_print
|
|
252
|
+
def list_partner_groups(controller, odata_filter, odata_orderby):
|
|
253
|
+
"""List all partner groups"""
|
|
254
|
+
with Spinner(description="Fetching partner groups"):
|
|
255
|
+
return controller.list_partner_groups(
|
|
256
|
+
filter=odata_filter, orderby=odata_orderby
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@group.command("sync")
|
|
261
|
+
@click.argument(
|
|
262
|
+
"partner",
|
|
263
|
+
type=click.Choice(
|
|
264
|
+
[c.name for c in Control if c != Control.INVALID], case_sensitive=False
|
|
265
|
+
),
|
|
266
|
+
required=True,
|
|
267
|
+
)
|
|
268
|
+
@click.option("--instance_id", required=True, help="instance ID of the partner")
|
|
269
|
+
@click.option("--group_ids", required=True, help="comma-separated list of group IDs")
|
|
270
|
+
@click.pass_obj
|
|
271
|
+
@pretty_print
|
|
272
|
+
def sync_groups(controller, partner, instance_id, group_ids):
|
|
273
|
+
"""Update groups for a partner"""
|
|
274
|
+
with Spinner(description="Updating groups"):
|
|
275
|
+
job_id = controller.update_partner_groups(
|
|
276
|
+
partner=Control[partner],
|
|
277
|
+
instance_id=instance_id,
|
|
278
|
+
group_ids=group_ids.split(","),
|
|
279
|
+
)["job_id"]
|
|
280
|
+
jobs = JobsController(account=controller.account)
|
|
281
|
+
while (result := jobs.job_status(job_id))["end_time"] is None:
|
|
282
|
+
sleep(3)
|
|
283
|
+
return result
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@click.group()
|
|
287
|
+
@click.pass_context
|
|
288
|
+
def threat(ctx):
|
|
289
|
+
"""SCM threat commands"""
|
|
290
|
+
ctx.obj = ScmController(account=ctx.obj.account)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
scm.add_command(threat)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@threat.command("create")
|
|
297
|
+
@click.argument("name")
|
|
298
|
+
@click.option(
|
|
299
|
+
"-d", "--description", help="description of the threat", default=None, type=str
|
|
300
|
+
)
|
|
301
|
+
@click.option("--id", help="uuid for threat", default=None, type=str)
|
|
302
|
+
@click.option(
|
|
303
|
+
"-g", "--generated", help="was the threat AI generated", default=False, type=bool
|
|
304
|
+
)
|
|
305
|
+
@click.option(
|
|
306
|
+
"-p", "--published", help="date the threat was published", default=None, type=str
|
|
307
|
+
)
|
|
308
|
+
@click.option(
|
|
309
|
+
"-s", "--source", help="source of threat (ex. www.cisa.gov)", default=None, type=str
|
|
310
|
+
)
|
|
311
|
+
@click.option(
|
|
312
|
+
"-i",
|
|
313
|
+
"--source_id",
|
|
314
|
+
help="ID of the threat, per the source (ex. aa23-075a)",
|
|
315
|
+
default=None,
|
|
316
|
+
type=str,
|
|
317
|
+
)
|
|
318
|
+
@click.option(
|
|
319
|
+
"-q",
|
|
320
|
+
"--techniques",
|
|
321
|
+
help="comma-separated list of techniques (MITRE ATT&CK IDs)",
|
|
322
|
+
default=None,
|
|
323
|
+
type=str,
|
|
324
|
+
)
|
|
325
|
+
@click.pass_obj
|
|
326
|
+
@pretty_print
|
|
327
|
+
def create_threat(
|
|
328
|
+
controller,
|
|
329
|
+
name,
|
|
330
|
+
description,
|
|
331
|
+
id,
|
|
332
|
+
generated,
|
|
333
|
+
published,
|
|
334
|
+
source,
|
|
335
|
+
source_id,
|
|
336
|
+
techniques,
|
|
337
|
+
):
|
|
338
|
+
"""Create an scm threat"""
|
|
339
|
+
with Spinner(description="Creating scm threat"):
|
|
340
|
+
return controller.create_threat(
|
|
341
|
+
name=name,
|
|
342
|
+
description=description,
|
|
343
|
+
id=id,
|
|
344
|
+
generated=generated,
|
|
345
|
+
published=published,
|
|
346
|
+
source=source,
|
|
347
|
+
source_id=source_id,
|
|
348
|
+
techniques=techniques,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@threat.command("delete")
|
|
353
|
+
@click.argument("threat_id")
|
|
354
|
+
@click.confirmation_option(prompt="Are you sure?")
|
|
355
|
+
@click.pass_obj
|
|
356
|
+
@pretty_print
|
|
357
|
+
def delete_threat(controller, threat_id):
|
|
358
|
+
"""Delete an scm threat"""
|
|
359
|
+
with Spinner(description="Removing scm threat"):
|
|
360
|
+
return controller.delete_threat(id=threat_id)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@threat.command("list")
|
|
364
|
+
@click.pass_obj
|
|
365
|
+
@pretty_print
|
|
366
|
+
def list_threats(controller):
|
|
367
|
+
"""List all scm threats"""
|
|
368
|
+
with Spinner(description="Fetching scm threats"):
|
|
369
|
+
return controller.list_threats()
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@threat.command("get")
|
|
373
|
+
@click.argument("threat_id")
|
|
374
|
+
@click.pass_obj
|
|
375
|
+
@pretty_print
|
|
376
|
+
def get_threat(controller, threat_id):
|
|
377
|
+
"""Get specific scm threat"""
|
|
378
|
+
with Spinner(description="Fetching scm threat"):
|
|
379
|
+
return controller.get_threat(id=threat_id)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@scm.command("threat-intel")
|
|
383
|
+
@click.argument(
|
|
384
|
+
"threat_pdf",
|
|
385
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
|
|
386
|
+
)
|
|
387
|
+
@click.pass_obj
|
|
388
|
+
@pretty_print
|
|
389
|
+
def parse_threat_intel(controller, threat_pdf):
|
|
390
|
+
with Spinner("Parsing PDF"):
|
|
391
|
+
return controller.parse_threat_intel(threat_pdf)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@scm.command("from-advisory")
|
|
395
|
+
@click.argument(
|
|
396
|
+
"partner", type=click.Choice([Control.CROWDSTRIKE.name], case_sensitive=False)
|
|
397
|
+
)
|
|
398
|
+
@click.option(
|
|
399
|
+
"-a", "--advisory_id", required=True, type=str, help="Partner advisory ID"
|
|
400
|
+
)
|
|
401
|
+
@click.pass_obj
|
|
402
|
+
@pretty_print
|
|
403
|
+
def parse_from_partner_advisory(controller, partner, advisory_id):
|
|
404
|
+
with Spinner("Uploading"):
|
|
405
|
+
return controller.parse_from_partner_advisory(
|
|
406
|
+
partner=Control[partner], advisory_id=advisory_id
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@click.group()
|
|
411
|
+
@click.pass_context
|
|
412
|
+
def exception(ctx):
|
|
413
|
+
"""SCM exception commands"""
|
|
414
|
+
ctx.obj = ScmController(account=ctx.obj.account)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@click.group()
|
|
418
|
+
@click.pass_context
|
|
419
|
+
def object(ctx):
|
|
420
|
+
"""SCM object exception commands"""
|
|
421
|
+
ctx.obj = ScmController(account=ctx.obj.account)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
@click.group()
|
|
425
|
+
@click.pass_context
|
|
426
|
+
def policy(ctx):
|
|
427
|
+
"""SCM policy exception commands"""
|
|
428
|
+
ctx.obj = ScmController(account=ctx.obj.account)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
exception.add_command(object)
|
|
432
|
+
exception.add_command(policy)
|
|
433
|
+
scm.add_command(exception)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@object.command("list")
|
|
437
|
+
@click.pass_obj
|
|
438
|
+
@pretty_print
|
|
439
|
+
def list_object_exceptions(controller):
|
|
440
|
+
"""List all object exceptions"""
|
|
441
|
+
with Spinner(description="Fetching object exceptions"):
|
|
442
|
+
return controller.list_object_exceptions()
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@object.command("create")
|
|
446
|
+
@click.argument(
|
|
447
|
+
"category",
|
|
448
|
+
type=click.Choice(
|
|
449
|
+
[
|
|
450
|
+
c.name
|
|
451
|
+
for c in ControlCategory
|
|
452
|
+
if c
|
|
453
|
+
not in [
|
|
454
|
+
ControlCategory.NONE,
|
|
455
|
+
ControlCategory.INVALID,
|
|
456
|
+
ControlCategory.PRIVATE_REPO,
|
|
457
|
+
]
|
|
458
|
+
],
|
|
459
|
+
case_sensitive=False,
|
|
460
|
+
),
|
|
461
|
+
)
|
|
462
|
+
@click.option(
|
|
463
|
+
"-f", "--filter", help="OData filter string", default=None, required=True, type=str
|
|
464
|
+
)
|
|
465
|
+
@click.option(
|
|
466
|
+
"-e",
|
|
467
|
+
"--expires",
|
|
468
|
+
help="expiry date (YYYY-MM-DD hh:mm:ss ([+-]hh:mm))",
|
|
469
|
+
default=None,
|
|
470
|
+
type=str,
|
|
471
|
+
)
|
|
472
|
+
@click.option("-n", "--name", help="exception name", default=None, type=str)
|
|
473
|
+
@click.pass_obj
|
|
474
|
+
@pretty_print
|
|
475
|
+
def create_object_exception(controller, category, filter, expires, name):
|
|
476
|
+
"""Create object exception"""
|
|
477
|
+
with Spinner(description=f"Creating object exception"):
|
|
478
|
+
return controller.create_object_exception(
|
|
479
|
+
category=ControlCategory[category],
|
|
480
|
+
filter=filter,
|
|
481
|
+
name=name,
|
|
482
|
+
expires=expires,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
@object.command("update")
|
|
487
|
+
@click.argument("exception_id", type=str)
|
|
488
|
+
@click.option(
|
|
489
|
+
"-e",
|
|
490
|
+
"--expires",
|
|
491
|
+
help="Expiry Date (YYYY-MM-DD hh:mm:ss ([+-]hh:mm))",
|
|
492
|
+
default=ScmController.default,
|
|
493
|
+
)
|
|
494
|
+
@click.option("-f", "--filter", help="OData filter string", default=None, type=str)
|
|
495
|
+
@click.option("-n", "--name", help="Exception Name", default=None, type=str)
|
|
496
|
+
@click.pass_obj
|
|
497
|
+
@pretty_print
|
|
498
|
+
def update_object_exception(controller, exception_id, expires, filter, name):
|
|
499
|
+
"""Update object exception"""
|
|
500
|
+
with Spinner(description=f"Updating object exception"):
|
|
501
|
+
return controller.update_object_exception(
|
|
502
|
+
exception_id=exception_id, filter=filter, name=name, expires=expires
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
@object.command("delete")
|
|
507
|
+
@click.argument("exception_id", type=str)
|
|
508
|
+
@click.confirmation_option(prompt="Are you sure?")
|
|
509
|
+
@click.pass_obj
|
|
510
|
+
@pretty_print
|
|
511
|
+
def delete_object_exception(controller, exception_id):
|
|
512
|
+
"""Delete object exception"""
|
|
513
|
+
with Spinner(description=f"Delete object exception"):
|
|
514
|
+
return controller.delete_object_exception(exception_id=exception_id)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
@policy.command("list")
|
|
518
|
+
@click.pass_obj
|
|
519
|
+
@pretty_print
|
|
520
|
+
def list_policy_exceptions(controller):
|
|
521
|
+
"""List all policy exceptions"""
|
|
522
|
+
with Spinner(description="Fetching policy exceptions"):
|
|
523
|
+
return controller.list_policy_exceptions()
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@policy.command("create")
|
|
527
|
+
@click.argument(
|
|
528
|
+
"partner",
|
|
529
|
+
type=click.Choice(
|
|
530
|
+
[c.name for c in Control if c != Control.INVALID], case_sensitive=False
|
|
531
|
+
),
|
|
532
|
+
)
|
|
533
|
+
@click.option("-i", "--instance_id", required=True, help="instance ID of the partner")
|
|
534
|
+
@click.option("-p", "--policy_id", required=True, help="ID of the policy to create")
|
|
535
|
+
@click.option(
|
|
536
|
+
"-s",
|
|
537
|
+
"--settings",
|
|
538
|
+
required=True,
|
|
539
|
+
help="Comma separated list of all setting names to be excluded",
|
|
540
|
+
)
|
|
541
|
+
@click.option(
|
|
542
|
+
"-e",
|
|
543
|
+
"--expires",
|
|
544
|
+
help="Expiry Date (YYYY-MM-DD hh:mm:ss ([+-]hh:mm))",
|
|
545
|
+
default=None,
|
|
546
|
+
type=str,
|
|
547
|
+
)
|
|
548
|
+
@click.pass_obj
|
|
549
|
+
@pretty_print
|
|
550
|
+
def create_policy_exception(
|
|
551
|
+
controller, partner, instance_id, policy_id, settings, expires
|
|
552
|
+
):
|
|
553
|
+
"""Create policy exception"""
|
|
554
|
+
with Spinner(description=f"Creating policy exception"):
|
|
555
|
+
return controller.create_policy_exceptions(
|
|
556
|
+
partner=Control[partner],
|
|
557
|
+
expires=expires,
|
|
558
|
+
instance_id=instance_id,
|
|
559
|
+
policy_id=policy_id,
|
|
560
|
+
setting_names=settings.split(",") if settings else None,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
@policy.command("update")
|
|
565
|
+
@click.argument(
|
|
566
|
+
"partner",
|
|
567
|
+
type=click.Choice(
|
|
568
|
+
[c.name for c in Control if c != Control.INVALID], case_sensitive=False
|
|
569
|
+
),
|
|
570
|
+
)
|
|
571
|
+
@click.option("-i", "--instance_id", required=True, help="instance ID of the partner")
|
|
572
|
+
@click.option("-p", "--policy_id", required=True, help="ID of the policy to update")
|
|
573
|
+
@click.option(
|
|
574
|
+
"-e",
|
|
575
|
+
"--expires",
|
|
576
|
+
help="Expiry Date (YYYY-MM-DD hh:mm:ss ([+-]hh:mm))",
|
|
577
|
+
default=ScmController.default,
|
|
578
|
+
)
|
|
579
|
+
@click.option(
|
|
580
|
+
"-s",
|
|
581
|
+
"--settings",
|
|
582
|
+
help="Comma separated list of all setting names to be excluded",
|
|
583
|
+
)
|
|
584
|
+
@click.pass_obj
|
|
585
|
+
@pretty_print
|
|
586
|
+
def update_policy_exception(
|
|
587
|
+
controller, partner, instance_id, policy_id, expires, settings
|
|
588
|
+
):
|
|
589
|
+
"""Update policy exception"""
|
|
590
|
+
with Spinner(description=f"Updating Policy exception"):
|
|
591
|
+
return controller.update_policy_exception(
|
|
592
|
+
partner=Control[partner],
|
|
593
|
+
expires=expires,
|
|
594
|
+
instance_id=instance_id,
|
|
595
|
+
policy_id=policy_id,
|
|
596
|
+
setting_names=settings.split(",") if settings else None,
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
@policy.command("delete")
|
|
601
|
+
@click.argument(
|
|
602
|
+
"partner",
|
|
603
|
+
type=click.Choice(
|
|
604
|
+
[c.name for c in Control if c != Control.INVALID], case_sensitive=False
|
|
605
|
+
),
|
|
606
|
+
)
|
|
607
|
+
@click.option("-i", "--instance_id", required=True, help="instance ID of the partner")
|
|
608
|
+
@click.option("-p", "--policy_id", required=True, help="ID of the policy to be deleted")
|
|
609
|
+
@click.confirmation_option(prompt="Are you sure?")
|
|
610
|
+
@click.pass_obj
|
|
611
|
+
@pretty_print
|
|
612
|
+
def delete_policy_exception(controller, partner, instance_id, policy_id):
|
|
613
|
+
"""Delete policy exception"""
|
|
614
|
+
with Spinner(description=f"Deleting Policy exception"):
|
|
615
|
+
return controller.delete_policy_exception(
|
|
616
|
+
instance_id=instance_id, policy_id=policy_id
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
"""Delete policy exception removes all exceptions in a policy"""
|
|
620
|
+
with Spinner(description=f"Deleting policy exception"):
|
|
621
|
+
return controller.put_policy_exceptions(
|
|
622
|
+
partner=Control[partner],
|
|
623
|
+
expires=None,
|
|
624
|
+
instance_id=instance_id,
|
|
625
|
+
policy_id=policy_id,
|
|
626
|
+
setting_names=[],
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
@click.group()
|
|
631
|
+
@click.pass_context
|
|
632
|
+
def notification(ctx):
|
|
633
|
+
"""SCM notification commands"""
|
|
634
|
+
ctx.obj = ScmController(account=ctx.obj.account)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
scm.add_command(notification)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@notification.command("list")
|
|
641
|
+
@click.pass_obj
|
|
642
|
+
@pretty_print
|
|
643
|
+
def list_notifications(controller):
|
|
644
|
+
with Spinner("Fetching notifications"):
|
|
645
|
+
return controller.list_notifications()
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
@notification.command("delete")
|
|
649
|
+
@click.argument("notification_id", type=str)
|
|
650
|
+
@click.confirmation_option(prompt="Are you sure?")
|
|
651
|
+
@click.pass_obj
|
|
652
|
+
@pretty_print
|
|
653
|
+
def delete_notification(controller, notification_id):
|
|
654
|
+
with Spinner("Deleting notification"):
|
|
655
|
+
return controller.delete_notification(notification_id)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
@notification.command("upsert")
|
|
659
|
+
@click.argument(
|
|
660
|
+
"control_category",
|
|
661
|
+
type=click.Choice([c.name for c in ControlCategory], case_sensitive=False),
|
|
662
|
+
)
|
|
663
|
+
@click.option(
|
|
664
|
+
"-e",
|
|
665
|
+
"--emails",
|
|
666
|
+
help="comma-separated list of emails to notify",
|
|
667
|
+
default=None,
|
|
668
|
+
type=str,
|
|
669
|
+
)
|
|
670
|
+
@click.option(
|
|
671
|
+
"-v",
|
|
672
|
+
"--event",
|
|
673
|
+
help="event to trigger notification for",
|
|
674
|
+
type=click.Choice([e.name for e in PartnerEvents], case_sensitive=False),
|
|
675
|
+
required=True,
|
|
676
|
+
)
|
|
677
|
+
@click.option("-f", "--filter", help="OData filter string", default=None, type=str)
|
|
678
|
+
@click.option(
|
|
679
|
+
"-i", "--id", help="ID of the notification to update", default=None, type=str
|
|
680
|
+
)
|
|
681
|
+
@click.option("-m", "--message", help="notification message", default="", type=str)
|
|
682
|
+
@click.option(
|
|
683
|
+
"-r",
|
|
684
|
+
"--run_code",
|
|
685
|
+
help="notification frequency",
|
|
686
|
+
type=click.Choice([r.name for r in RunCode], case_sensitive=False),
|
|
687
|
+
required=True,
|
|
688
|
+
)
|
|
689
|
+
@click.option(
|
|
690
|
+
"-s",
|
|
691
|
+
"--scheduled_hour",
|
|
692
|
+
help="scheduled UTC hour to receive notifications",
|
|
693
|
+
type=int,
|
|
694
|
+
required=True,
|
|
695
|
+
)
|
|
696
|
+
@click.option(
|
|
697
|
+
"--slack_urls",
|
|
698
|
+
help="comma-separated list of Slack Webhook URLs to notify",
|
|
699
|
+
default=None,
|
|
700
|
+
type=str,
|
|
701
|
+
)
|
|
702
|
+
@click.option(
|
|
703
|
+
"-p",
|
|
704
|
+
"--suppress_empty",
|
|
705
|
+
help="suppress notifications with no results",
|
|
706
|
+
default=True,
|
|
707
|
+
type=bool,
|
|
708
|
+
)
|
|
709
|
+
@click.option(
|
|
710
|
+
"--teams_urls",
|
|
711
|
+
help="comma-separated list of Teams Webhook URLs to notify",
|
|
712
|
+
default=None,
|
|
713
|
+
type=str,
|
|
714
|
+
)
|
|
715
|
+
@click.option(
|
|
716
|
+
"-t", "--title", help="notification title", default="SCM Notification", type=str
|
|
717
|
+
)
|
|
718
|
+
@click.pass_obj
|
|
719
|
+
@pretty_print
|
|
720
|
+
def upsert_notification(
|
|
721
|
+
controller,
|
|
722
|
+
control_category,
|
|
723
|
+
emails,
|
|
724
|
+
event,
|
|
725
|
+
filter,
|
|
726
|
+
id,
|
|
727
|
+
message,
|
|
728
|
+
run_code,
|
|
729
|
+
scheduled_hour,
|
|
730
|
+
slack_urls,
|
|
731
|
+
suppress_empty,
|
|
732
|
+
teams_urls,
|
|
733
|
+
title,
|
|
734
|
+
):
|
|
735
|
+
"""Upsert an SCM notification"""
|
|
736
|
+
with Spinner("Upserting notification"):
|
|
737
|
+
return controller.upsert_notification(
|
|
738
|
+
control_category=ControlCategory[control_category],
|
|
739
|
+
emails=emails.split(",") if emails else None,
|
|
740
|
+
event=PartnerEvents[event],
|
|
741
|
+
filter=filter,
|
|
742
|
+
id=id,
|
|
743
|
+
message=message,
|
|
744
|
+
run_code=RunCode[run_code],
|
|
745
|
+
scheduled_hour=scheduled_hour,
|
|
746
|
+
slack_urls=slack_urls.split(",") if slack_urls else None,
|
|
747
|
+
suppress_empty=suppress_empty,
|
|
748
|
+
teams_urls=teams_urls.split(",") if teams_urls else None,
|
|
749
|
+
title=title,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
@scm.command("notations")
|
|
754
|
+
@click.pass_obj
|
|
755
|
+
@pretty_print
|
|
756
|
+
def list_notations(controller):
|
|
757
|
+
"""List all notations"""
|
|
758
|
+
with Spinner("Fetching notations"):
|
|
759
|
+
return controller.list_notations()
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
@scm.command("history")
|
|
763
|
+
@click.option("--odata_filter", help="OData filter string", default=None)
|
|
764
|
+
@click.option("--start", type=str, default=None, help="start date")
|
|
765
|
+
@click.option("--end", type=str, default=None, help="end date")
|
|
766
|
+
@click.pass_obj
|
|
767
|
+
@pretty_print
|
|
768
|
+
def list_history(controller, odata_filter, start, end):
|
|
769
|
+
"""List history"""
|
|
770
|
+
with Spinner("Fetching SCM history"):
|
|
771
|
+
return controller.list_history(start, end, filter=odata_filter)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
@click.group()
|
|
775
|
+
@click.pass_context
|
|
776
|
+
def report(ctx):
|
|
777
|
+
"""SCM report commands"""
|
|
778
|
+
ctx.obj = ScmController(account=ctx.obj.account)
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
scm.add_command(report)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
@report.command("get")
|
|
785
|
+
@click.argument("report_id", type=str)
|
|
786
|
+
@click.pass_obj
|
|
787
|
+
@pretty_print
|
|
788
|
+
def get_report(controller, report_id):
|
|
789
|
+
with Spinner("Fetching report"):
|
|
790
|
+
return controller.get_report(report_id)
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
@report.command("list")
|
|
794
|
+
@click.pass_obj
|
|
795
|
+
@pretty_print
|
|
796
|
+
def list_reports(controller):
|
|
797
|
+
with Spinner("Fetching reports"):
|
|
798
|
+
return controller.list_reports()
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
@report.command("delete")
|
|
802
|
+
@click.argument("report_id", type=str)
|
|
803
|
+
@click.confirmation_option(prompt="Are you sure?")
|
|
804
|
+
@click.pass_obj
|
|
805
|
+
@pretty_print
|
|
806
|
+
def delete_report(controller, report_id):
|
|
807
|
+
with Spinner("Deleting report"):
|
|
808
|
+
return controller.delete_report(report_id)
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
@report.command("put")
|
|
812
|
+
@click.option(
|
|
813
|
+
"--report_data",
|
|
814
|
+
type=str,
|
|
815
|
+
help="report data in JSON format, cannot be used with report_file",
|
|
816
|
+
default=None,
|
|
817
|
+
)
|
|
818
|
+
@click.option(
|
|
819
|
+
"--report_file",
|
|
820
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
|
|
821
|
+
help="report data JSON file, will ignore report_data if provided",
|
|
822
|
+
default=None,
|
|
823
|
+
)
|
|
824
|
+
@click.option("--report_id", type=str, help="report ID to update", default=None)
|
|
825
|
+
@click.pass_obj
|
|
826
|
+
@pretty_print
|
|
827
|
+
def put_report(controller, report_data, report_file, report_id):
|
|
828
|
+
if not report_data and not report_file:
|
|
829
|
+
raise ValueError("Either report_data or report_file must be provided")
|
|
830
|
+
|
|
831
|
+
with Spinner("Updating report"):
|
|
832
|
+
if report_file:
|
|
833
|
+
with open(report_file, "r") as f:
|
|
834
|
+
report_data = f.read()
|
|
835
|
+
report_data = json.loads(report_data)
|
|
836
|
+
return controller.put_report(report_data, report_id)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
@report.command("chart-data")
|
|
840
|
+
@click.argument(
|
|
841
|
+
"scm_category",
|
|
842
|
+
type=click.Choice(
|
|
843
|
+
[c.name for c in SCMCategory if c.value > 0], case_sensitive=False
|
|
844
|
+
),
|
|
845
|
+
)
|
|
846
|
+
@click.option("--group_by", "-b", help="field to group by", required=True, type=str)
|
|
847
|
+
@click.option(
|
|
848
|
+
"--group_limit", "-l", help="max number of groups to return", type=int, default=100
|
|
849
|
+
)
|
|
850
|
+
@click.option(
|
|
851
|
+
"--sort_by",
|
|
852
|
+
"-s",
|
|
853
|
+
help="sort method",
|
|
854
|
+
type=click.Choice(["a-z", "z-a", "0-9", "9-0"]),
|
|
855
|
+
default="9-0",
|
|
856
|
+
)
|
|
857
|
+
@click.option(
|
|
858
|
+
"--scopes",
|
|
859
|
+
"-c",
|
|
860
|
+
help="comma-separated list of scope to value pairs, i.e. instances/control=1,instances/platform=windows",
|
|
861
|
+
default=None,
|
|
862
|
+
type=str,
|
|
863
|
+
)
|
|
864
|
+
@click.option(
|
|
865
|
+
"--odata_filter", "-f", help="OData filter string", default=None, type=str
|
|
866
|
+
)
|
|
867
|
+
@click.pass_obj
|
|
868
|
+
@pretty_print
|
|
869
|
+
def get_chart_data(
|
|
870
|
+
controller, scm_category, group_by, group_limit, sort_by, scopes, odata_filter
|
|
871
|
+
):
|
|
872
|
+
"""Get chart data for SCM reports"""
|
|
873
|
+
with Spinner("Fetching chart data"):
|
|
874
|
+
return controller.get_chart_data(
|
|
875
|
+
scm_category=SCMCategory[scm_category],
|
|
876
|
+
group_by=group_by,
|
|
877
|
+
group_limit=group_limit,
|
|
878
|
+
sort_by=sort_by,
|
|
879
|
+
scopes=scopes,
|
|
880
|
+
odata_filter=odata_filter,
|
|
881
|
+
)
|