pyntcli 0.1.108__py3-none-any.whl → 0.1.110__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.
- pyntcli/__init__.py +1 -1
- pyntcli/commands/burp.py +61 -14
- pyntcli/commands/command.py +2 -2
- pyntcli/commands/listen.py +2 -2
- pyntcli/commands/newman.py +2 -3
- pyntcli/commands/pynt_cmd.py +5 -6
- pyntcli/commands/template.py +182 -0
- pyntcli/commands/util.py +3 -1
- pyntcli/pynt_docker/pynt_container.py +17 -1
- {pyntcli-0.1.108.dist-info → pyntcli-0.1.110.dist-info}/METADATA +1 -1
- {pyntcli-0.1.108.dist-info → pyntcli-0.1.110.dist-info}/RECORD +14 -13
- {pyntcli-0.1.108.dist-info → pyntcli-0.1.110.dist-info}/WHEEL +1 -1
- {pyntcli-0.1.108.dist-info → pyntcli-0.1.110.dist-info}/entry_points.txt +0 -0
- {pyntcli-0.1.108.dist-info → pyntcli-0.1.110.dist-info}/top_level.txt +0 -0
pyntcli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.110"
|
pyntcli/commands/burp.py
CHANGED
|
@@ -10,6 +10,9 @@ from subprocess import Popen, PIPE
|
|
|
10
10
|
from functools import partial
|
|
11
11
|
import xmltodict
|
|
12
12
|
import base64
|
|
13
|
+
|
|
14
|
+
from pyntcli.store import store
|
|
15
|
+
|
|
13
16
|
import pyntcli.log.log as log
|
|
14
17
|
from xml.parsers.expat import ExpatError
|
|
15
18
|
|
|
@@ -19,7 +22,6 @@ from pyntcli.commands import util, sub_command
|
|
|
19
22
|
from pyntcli.ui import report as cli_reporter
|
|
20
23
|
from pyntcli.transport import pynt_requests
|
|
21
24
|
|
|
22
|
-
|
|
23
25
|
methods = [
|
|
24
26
|
"get",
|
|
25
27
|
"post",
|
|
@@ -138,9 +140,8 @@ def burp_usage():
|
|
|
138
140
|
.with_line("Options:", style=ui_thread.PrinterText.HEADER)
|
|
139
141
|
.with_line("\t--xml - Path to the xml to run tests on")
|
|
140
142
|
.with_line('\t--captured-domains - Pynt will scan only these domains and subdomains. For all domains write "*"')
|
|
141
|
-
.with_line("\t--port - Set the port pynt will listen to (DEFAULT:
|
|
143
|
+
.with_line("\t--port - Set the port pynt will listen to (DEFAULT: random)")
|
|
142
144
|
.with_line("\t--ca-path - The path to the CA file in PEM format")
|
|
143
|
-
.with_line("\t--proxy-port - Set the port proxied traffic should be routed to (DEFAULT: 6666)")
|
|
144
145
|
.with_line("\t--report - If present will save the generated report in this path.")
|
|
145
146
|
.with_line("\t--insecure - Use when target uses self signed certificates")
|
|
146
147
|
.with_line("\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io")
|
|
@@ -163,23 +164,23 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
163
164
|
|
|
164
165
|
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
165
166
|
burp_cmd = parent.add_parser(self.name)
|
|
166
|
-
burp_cmd.add_argument("--port", "-p", help="", type=int, default=
|
|
167
|
-
burp_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
167
|
+
burp_cmd.add_argument("--port", "-p", help="", type=int, default=util.find_open_port())
|
|
168
168
|
burp_cmd.add_argument("--xml", help="", default="", required=True)
|
|
169
169
|
burp_cmd.add_argument("--ca-path", type=str, default="")
|
|
170
170
|
burp_cmd.add_argument("--report", type=str, default="")
|
|
171
|
+
burp_cmd.add_argument("--save-collection", action="store_true", help="Get postman collection")
|
|
171
172
|
burp_cmd.add_argument("--captured-domains", nargs="+", help="", default="")
|
|
172
173
|
burp_cmd.add_argument("--severity-level", choices=["all", "medium", "high", "critical", "none"], default="none")
|
|
173
174
|
burp_cmd.print_usage = self.print_usage
|
|
174
175
|
burp_cmd.print_help = self.print_usage
|
|
175
176
|
return burp_cmd
|
|
176
177
|
|
|
177
|
-
def _updated_environment(self,
|
|
178
|
+
def _updated_environment(self, proxy_port: int):
|
|
178
179
|
env_copy = deepcopy(os.environ)
|
|
179
180
|
return env_copy.update(
|
|
180
181
|
{
|
|
181
|
-
"HTTP_PROXY": "http://localhost:{}".format(
|
|
182
|
-
"HTTPS_PROXY": "http://localhost:{}".format(
|
|
182
|
+
"HTTP_PROXY": "http://localhost:{}".format(proxy_port),
|
|
183
|
+
"HTTPS_PROXY": "http://localhost:{}".format(proxy_port),
|
|
183
184
|
}
|
|
184
185
|
)
|
|
185
186
|
|
|
@@ -224,12 +225,43 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
224
225
|
ui_thread.print("Error in polling for scan report: {}".format(res.text))
|
|
225
226
|
return
|
|
226
227
|
|
|
228
|
+
def _get_postman_collection(self, args):
|
|
229
|
+
while True:
|
|
230
|
+
res = pynt_requests.get(
|
|
231
|
+
self.proxy_server_base_url.format(args.port)
|
|
232
|
+
+ "/collection",
|
|
233
|
+
params={"scanId": self.scan_id},
|
|
234
|
+
)
|
|
235
|
+
if res.status_code == HTTPStatus.OK:
|
|
236
|
+
return res.text
|
|
237
|
+
if res.status_code == HTTPStatus.ACCEPTED:
|
|
238
|
+
time.sleep(self.proxy_sleep_interval)
|
|
239
|
+
continue
|
|
240
|
+
if res.status_code == 517: # pynt did not recieve any requests
|
|
241
|
+
ui_thread.print(
|
|
242
|
+
ui_thread.PrinterText(
|
|
243
|
+
res.json()["message"], ui_thread.PrinterText.WARNING
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
return
|
|
247
|
+
if res.status_code == 404:
|
|
248
|
+
ui_thread.print(
|
|
249
|
+
ui_thread.PrinterText(
|
|
250
|
+
"No collection found", ui_thread.PrinterText.WARNING
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
return
|
|
254
|
+
ui_thread.print("Error in polling for scan report: {}".format(res.text))
|
|
255
|
+
return
|
|
256
|
+
|
|
227
257
|
def run_cmd(self, args: argparse.Namespace):
|
|
258
|
+
proxy_port = util.find_open_port()
|
|
259
|
+
|
|
228
260
|
container_config = pynt_container.DockerContainerConfig(
|
|
229
261
|
args,
|
|
230
262
|
"proxy",
|
|
231
263
|
pynt_container.api_port(args.port),
|
|
232
|
-
pynt_container.proxy_port(
|
|
264
|
+
pynt_container.proxy_port(proxy_port),
|
|
233
265
|
)
|
|
234
266
|
|
|
235
267
|
for host in args.captured_domains:
|
|
@@ -301,7 +333,7 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
301
333
|
|
|
302
334
|
self._start_proxy(args)
|
|
303
335
|
|
|
304
|
-
run_burp_xml(doc,
|
|
336
|
+
run_burp_xml(doc, proxy_port)
|
|
305
337
|
|
|
306
338
|
self._stop_proxy(args)
|
|
307
339
|
|
|
@@ -313,10 +345,10 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
313
345
|
)
|
|
314
346
|
|
|
315
347
|
with ui_thread.progress(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
348
|
+
"ws://localhost:{}/progress?scanId={}".format(args.port, self.scan_id),
|
|
349
|
+
partial(lambda *args: None),
|
|
350
|
+
"scan in progress...",
|
|
351
|
+
100,
|
|
320
352
|
):
|
|
321
353
|
html_report = self._get_report(args, "html")
|
|
322
354
|
html_report_path = os.path.join(
|
|
@@ -328,6 +360,11 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
328
360
|
tempfile.gettempdir(), "pynt_report_{}.json".format(int(time.time()))
|
|
329
361
|
)
|
|
330
362
|
|
|
363
|
+
collection = self._get_postman_collection(args) if args.save_collection else None
|
|
364
|
+
collection_path = os.path.join(
|
|
365
|
+
store.get_default_store_dir(), "postman_collection_{}.json".format(int(time.time()))
|
|
366
|
+
)
|
|
367
|
+
|
|
331
368
|
if "report" in args and args.report:
|
|
332
369
|
full_path = os.path.abspath(args.report)
|
|
333
370
|
html_report_path = util.get_user_report_path(full_path, "html")
|
|
@@ -344,6 +381,16 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
344
381
|
reporter = cli_reporter.PyntReporter(json_report_path)
|
|
345
382
|
reporter.print_summary()
|
|
346
383
|
|
|
384
|
+
if collection:
|
|
385
|
+
with open(collection_path, "w", encoding="utf-8") as collection_file:
|
|
386
|
+
collection_file.write(collection)
|
|
387
|
+
ui_thread.print(
|
|
388
|
+
ui_thread.PrinterText(
|
|
389
|
+
"Postman collection saved at: {}".format(collection_path),
|
|
390
|
+
ui_thread.PrinterText.INFO,
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
|
|
347
394
|
if json_report:
|
|
348
395
|
json_obj = json.loads(json_report)
|
|
349
396
|
if json_obj:
|
pyntcli/commands/command.py
CHANGED
|
@@ -29,7 +29,7 @@ def command_usage():
|
|
|
29
29
|
.with_line("\t--cmd - The command that runs the functional tests")
|
|
30
30
|
.with_line('\t--captured-domains - Pynt will scan only these domains and subdomains. For all domains write "*"')
|
|
31
31
|
.with_line('\t--test-name - A name for your Pynt scan')
|
|
32
|
-
.with_line("\t--port - Set the port pynt will listen to (DEFAULT:
|
|
32
|
+
.with_line("\t--port - Set the port pynt will listen to (DEFAULT: random)")
|
|
33
33
|
.with_line("\t--allow-errors - If present will allow command to fail and continue execution")
|
|
34
34
|
.with_line("\t--ca-path - The path to the CA file in PEM format")
|
|
35
35
|
.with_line("\t--proxy-port - Set the port proxied traffic should be routed to (DEFAULT: 6666)")
|
|
@@ -57,7 +57,7 @@ class CommandSubCommand(sub_command.PyntSubCommand):
|
|
|
57
57
|
|
|
58
58
|
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
59
59
|
proxy_cmd = parent.add_parser(self.name)
|
|
60
|
-
proxy_cmd.add_argument("--port", "-p", help="", type=int, default=
|
|
60
|
+
proxy_cmd.add_argument("--port", "-p", help="", type=int, default=util.find_open_port())
|
|
61
61
|
proxy_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
62
62
|
proxy_cmd.add_argument("--cmd", help="", default="", required=True)
|
|
63
63
|
proxy_cmd.add_argument("--captured-domains", nargs="+", help="", default="", required=False)
|
pyntcli/commands/listen.py
CHANGED
|
@@ -26,7 +26,7 @@ def listen_usage():
|
|
|
26
26
|
.with_line("Options:", style=ui_thread.PrinterText.HEADER)
|
|
27
27
|
.with_line('\t--captured-domains - Pynt will scan only these domains and subdomains. For all domains write "*"')
|
|
28
28
|
.with_line('\t--test-name - A name for your Pynt scan')
|
|
29
|
-
.with_line("\t--port - Set the port pynt will listen to (DEFAULT:
|
|
29
|
+
.with_line("\t--port - Set the port pynt will listen to (DEFAULT: random)")
|
|
30
30
|
.with_line("\t--ca-path - The path to the CA file in PEM format")
|
|
31
31
|
.with_line("\t--proxy-port - Set the port proxied traffic should be routed to (DEFAULT: 6666)")
|
|
32
32
|
.with_line("\t--report - If present will save the generated report in this path.")
|
|
@@ -51,7 +51,7 @@ class ListenSubCommand(sub_command.PyntSubCommand):
|
|
|
51
51
|
|
|
52
52
|
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
53
53
|
listen_cmd = parent.add_parser(self.name)
|
|
54
|
-
listen_cmd.add_argument("--port", "-p", help="", type=int, default=
|
|
54
|
+
listen_cmd.add_argument("--port", "-p", help="", type=int, default=util.find_open_port())
|
|
55
55
|
listen_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
56
56
|
listen_cmd.add_argument("--captured-domains", nargs="+", help="", default="", required=True)
|
|
57
57
|
listen_cmd.add_argument("--test-name", help="", default="", required=False)
|
pyntcli/commands/newman.py
CHANGED
|
@@ -53,7 +53,6 @@ class NewmanSubCommand(sub_command.PyntSubCommand):
|
|
|
53
53
|
return newman_cmd
|
|
54
54
|
|
|
55
55
|
def run_cmd(self, args: argparse.Namespace):
|
|
56
|
-
|
|
57
56
|
port = util.find_open_port()
|
|
58
57
|
container_config = pynt_container.DockerContainerConfig(
|
|
59
58
|
args,
|
|
@@ -115,8 +114,8 @@ class NewmanSubCommand(sub_command.PyntSubCommand):
|
|
|
115
114
|
ui_thread.print(ui_thread.PrinterText(
|
|
116
115
|
"Pynt docker is ready",
|
|
117
116
|
ui_thread.PrinterText.INFO,
|
|
118
|
-
))
|
|
119
|
-
|
|
117
|
+
))
|
|
118
|
+
|
|
120
119
|
ui_thread.print_generator(newman_docker.stdout)
|
|
121
120
|
|
|
122
121
|
with ui_thread.progress(
|
pyntcli/commands/pynt_cmd.py
CHANGED
|
@@ -12,8 +12,7 @@ import pyntcli.log.log as log
|
|
|
12
12
|
|
|
13
13
|
from requests.exceptions import SSLError, HTTPError
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
from . import command, listen, postman, root, sub_command, id_command, newman, har, burp
|
|
15
|
+
from . import command, listen, postman, root, sub_command, id_command, newman, har, burp, template
|
|
17
16
|
|
|
18
17
|
logger = log.get_logger()
|
|
19
18
|
|
|
@@ -25,6 +24,7 @@ avail_sub_commands = [
|
|
|
25
24
|
command.CommandSubCommand("command"),
|
|
26
25
|
listen.ListenSubCommand("listen"),
|
|
27
26
|
burp.BurpCommand("burp"),
|
|
27
|
+
template.TemplateSubCommand("template")
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
commands_without_app_id = ["postman", "pynt-id"]
|
|
@@ -50,7 +50,6 @@ VERSION_CHECK_URL = "https://d1efigcr4c19qn.cloudfront.net/cli/version"
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def check_is_latest_version(current_version):
|
|
53
|
-
|
|
54
53
|
try:
|
|
55
54
|
res = pynt_requests.get(VERSION_CHECK_URL)
|
|
56
55
|
res.raise_for_status()
|
|
@@ -175,9 +174,9 @@ class PyntCommand:
|
|
|
175
174
|
return True
|
|
176
175
|
|
|
177
176
|
if prompt.confirmation_prompt_with_timeout("Application ID is missing. Use the '--application-id' flag to provide it.\n" +
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
"Without an Application ID, the scan will not be associated with your application.\n" +
|
|
178
|
+
"The Application ID can be fetched from https://app.pynt.io/dashboard/applications.\n" +
|
|
179
|
+
"Do you want to continue without associating the scan?", default="yes", timeout=15):
|
|
181
180
|
prompts_history["missing_app_id"] = {"last_confirmation": current_time.strftime("%Y-%m-%d %H:%M:%S")}
|
|
182
181
|
state_store.put_prompts_history(
|
|
183
182
|
prompts_history)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
import time
|
|
6
|
+
from functools import partial
|
|
7
|
+
|
|
8
|
+
from pyntcli.pynt_docker import pynt_container
|
|
9
|
+
from pyntcli.ui import ui_thread
|
|
10
|
+
from pyntcli.commands import sub_command, util
|
|
11
|
+
|
|
12
|
+
PYNT_CONTAINER_INTERNAL_PORT = "5001"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def template_usage():
|
|
16
|
+
return (
|
|
17
|
+
ui_thread.PrinterText("Integration with template testing")
|
|
18
|
+
.with_line("")
|
|
19
|
+
.with_line("Usage:", style=ui_thread.PrinterText.HEADER)
|
|
20
|
+
.with_line("\tpynt template [OPTIONS]")
|
|
21
|
+
.with_line("")
|
|
22
|
+
.with_line("Options:", style=ui_thread.PrinterText.HEADER)
|
|
23
|
+
.with_line("\t--template-file / -tf - Path to the template file")
|
|
24
|
+
.with_line("\t--template-paths-file / -tpf - Path to the file containing the template paths (with new line separator) for the attacks")
|
|
25
|
+
.with_line("\t--url - Base URL for the attacks")
|
|
26
|
+
.with_line("\t--urls - Path to the file containing the base URLs (with new line separator) for the attacks")
|
|
27
|
+
.with_line("\t--reporters - Output results to json")
|
|
28
|
+
.with_line("\t--severity-level - 'all', 'medium', 'high', 'critical', 'none' (default)")
|
|
29
|
+
.with_line("\t--verbose - Use to get more detailed information about the run")
|
|
30
|
+
.with_line("")
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TemplateSubCommand(sub_command.PyntSubCommand):
|
|
35
|
+
def __init__(self, name) -> None:
|
|
36
|
+
super().__init__(name)
|
|
37
|
+
|
|
38
|
+
def usage(self, *args):
|
|
39
|
+
ui_thread.print(template_usage())
|
|
40
|
+
|
|
41
|
+
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
42
|
+
template_cmd = parent.add_parser(self.name)
|
|
43
|
+
template_cmd.add_argument("--template-file", "-tf", type=str, help="Path to the template file")
|
|
44
|
+
template_cmd.add_argument("--template-paths-file", "-tpf", type=str, help="Path to the file containing the template paths (with new line separator) for the attacks")
|
|
45
|
+
template_cmd.add_argument("--url", "-u", type=str, help="Base URL for the attacks")
|
|
46
|
+
template_cmd.add_argument("--urls", type=str, help="Path to the file containing the base URLs (with new line separator) for the attacks")
|
|
47
|
+
template_cmd.add_argument("--reporters", action="store_true")
|
|
48
|
+
template_cmd.add_argument("--severity-level", choices=["all", "medium", "high", "critical", "none"], default="none")
|
|
49
|
+
template_cmd.print_usage = self.usage
|
|
50
|
+
template_cmd.print_help = self.usage
|
|
51
|
+
return template_cmd
|
|
52
|
+
|
|
53
|
+
def run_cmd(self, args: argparse.Namespace):
|
|
54
|
+
pynt_folder_path = "/etc/pynt"
|
|
55
|
+
templates_folder_path = f"{pynt_folder_path}/templates"
|
|
56
|
+
ui_thread.print_verbose("Building container")
|
|
57
|
+
port = util.find_open_port()
|
|
58
|
+
container_config = pynt_container.DockerContainerConfig(
|
|
59
|
+
args,
|
|
60
|
+
"template",
|
|
61
|
+
pynt_container._PyntDockerPort(port=port, name="--port")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
validate_args(args)
|
|
66
|
+
except ValueError as e:
|
|
67
|
+
ui_thread.print(
|
|
68
|
+
ui_thread.PrinterText(
|
|
69
|
+
str(e),
|
|
70
|
+
ui_thread.PrinterText.WARNING,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
if args.template_file:
|
|
76
|
+
if not os.path.isfile(args.template_file):
|
|
77
|
+
ui_thread.print(
|
|
78
|
+
ui_thread.PrinterText(
|
|
79
|
+
"Could not find the provided path, please provide a valid path",
|
|
80
|
+
ui_thread.PrinterText.WARNING,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
return
|
|
84
|
+
template_name = os.path.basename(args.template_file)
|
|
85
|
+
full_template_path = f"{templates_folder_path}/{template_name}"
|
|
86
|
+
container_config.mounts.append(
|
|
87
|
+
pynt_container.create_mount(
|
|
88
|
+
os.path.abspath(args.template_file), full_template_path
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
container_config.docker_arguments += ["--template-file", full_template_path]
|
|
92
|
+
|
|
93
|
+
if args.template_paths_file:
|
|
94
|
+
if not os.path.isfile(args.template_paths_file):
|
|
95
|
+
ui_thread.print(
|
|
96
|
+
ui_thread.PrinterText(
|
|
97
|
+
"Could not find the provided paths file, please provide a valid paths file",
|
|
98
|
+
ui_thread.PrinterText.WARNING,
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# read the file, mount each uncommented file to /etc/pynt/templates, create a temp file with the paths as they are in the container (in /etc/pynt) and mount it to /etc/pynt/paths.txt
|
|
104
|
+
with open(args.template_paths_file, "r") as f:
|
|
105
|
+
paths = [line.strip() for line in f if not (line.strip().startswith("#") or line.startswith("//"))]
|
|
106
|
+
for path in paths:
|
|
107
|
+
container_config.mounts.append(
|
|
108
|
+
pynt_container.create_mount(
|
|
109
|
+
os.path.abspath(path), f"{templates_folder_path}/{os.path.basename(path)}"
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
container_config.docker_arguments += ["--templates-folder", templates_folder_path]
|
|
114
|
+
|
|
115
|
+
if args.url:
|
|
116
|
+
container_config.docker_arguments += ["--url", args.url]
|
|
117
|
+
|
|
118
|
+
if args.urls and not os.path.isfile(args.urls):
|
|
119
|
+
ui_thread.print(
|
|
120
|
+
ui_thread.PrinterText(
|
|
121
|
+
"Could not find the provided urls file, please provide a valid urls file",
|
|
122
|
+
ui_thread.PrinterText.WARNING,
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
if args.urls:
|
|
128
|
+
base_urls_path = f"{pynt_folder_path}/base_urls.txt"
|
|
129
|
+
container_config.docker_arguments += ["--urls", base_urls_path]
|
|
130
|
+
container_config.mounts.append(
|
|
131
|
+
pynt_container.create_mount(
|
|
132
|
+
os.path.abspath(args.urls), base_urls_path
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
with util.create_default_file_mounts(args) as m:
|
|
137
|
+
container_config.mounts += m
|
|
138
|
+
|
|
139
|
+
template_docker = pynt_container.PyntContainerNative(container_config)
|
|
140
|
+
|
|
141
|
+
template_docker.prepare_client()
|
|
142
|
+
template_docker.pre_run_validation(port)
|
|
143
|
+
template_docker.run()
|
|
144
|
+
|
|
145
|
+
healthcheck = partial(
|
|
146
|
+
util.wait_for_healthcheck, "http://localhost:{}".format(port)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
healthcheck()
|
|
150
|
+
ui_thread.print_verbose(util.GOT_INITIAL_HEALTHCHECK_MESSAGE)
|
|
151
|
+
ui_thread.print(ui_thread.PrinterText(
|
|
152
|
+
"Pynt docker is ready",
|
|
153
|
+
ui_thread.PrinterText.INFO,
|
|
154
|
+
))
|
|
155
|
+
|
|
156
|
+
ui_thread.print_generator(template_docker.stdout)
|
|
157
|
+
|
|
158
|
+
with ui_thread.progress(
|
|
159
|
+
"ws://localhost:{}/progress".format(port),
|
|
160
|
+
healthcheck,
|
|
161
|
+
"scan in progress...",
|
|
162
|
+
100,
|
|
163
|
+
):
|
|
164
|
+
while template_docker.is_alive():
|
|
165
|
+
time.sleep(1)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def validate_args(args: argparse.Namespace):
|
|
169
|
+
if not args.template_file and not args.template_paths_file:
|
|
170
|
+
raise ValueError("Please provide either a template file or a template paths file")
|
|
171
|
+
if args.template_file and args.template_paths_file:
|
|
172
|
+
raise ValueError("Please provide either a template file or a template paths file, not both")
|
|
173
|
+
if not args.url and not args.urls:
|
|
174
|
+
raise ValueError("Please provide either a url or a urls file")
|
|
175
|
+
if args.url and args.urls:
|
|
176
|
+
raise ValueError("Please provide either a url or a urls file, not both")
|
|
177
|
+
if args.urls and not os.path.isfile(args.urls):
|
|
178
|
+
raise ValueError("Could not find the provided urls file, please provide a valid urls file")
|
|
179
|
+
if args.template_file and not os.path.isfile(args.template_file):
|
|
180
|
+
raise ValueError("Could not find the provided path, please provide a valid path")
|
|
181
|
+
if args.template_paths_file and not os.path.isfile(args.template_paths_file):
|
|
182
|
+
raise ValueError("Could not find the provided paths file, please provide a valid paths file")
|
pyntcli/commands/util.py
CHANGED
|
@@ -10,7 +10,7 @@ import pyntcli.store.store as store
|
|
|
10
10
|
|
|
11
11
|
from pyntcli.commands.static_file_extensions import STATIC_FILE_EXTENSIONS
|
|
12
12
|
from pyntcli.pynt_docker import pynt_container
|
|
13
|
-
from pyntcli.ui import report
|
|
13
|
+
from pyntcli.ui import report, ui_thread
|
|
14
14
|
from pyntcli.transport import pynt_requests
|
|
15
15
|
|
|
16
16
|
logger = log.get_logger()
|
|
@@ -37,6 +37,7 @@ GOT_INITIAL_HEALTHCHECK_MESSAGE = "Got initial pynt server health check"
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def wait_for_healthcheck(address):
|
|
40
|
+
ui_thread.print_verbose("Waiting for healthcheck...")
|
|
40
41
|
start = time.time()
|
|
41
42
|
while start + HEALTHCHECK_TIMEOUT > time.time():
|
|
42
43
|
try:
|
|
@@ -49,6 +50,7 @@ def wait_for_healthcheck(address):
|
|
|
49
50
|
time.sleep(HEALTHCHECK_INTERVAL)
|
|
50
51
|
|
|
51
52
|
logger.debug("Health check timed out!")
|
|
53
|
+
ui_thread.print_verbose(f"Request to {address}/healthcheck timed out")
|
|
52
54
|
raise TimeoutError()
|
|
53
55
|
|
|
54
56
|
|
|
@@ -240,6 +240,10 @@ class PyntContainerNative:
|
|
|
240
240
|
docker_command += args
|
|
241
241
|
|
|
242
242
|
command = self.adapt_run_command(docker_command)
|
|
243
|
+
|
|
244
|
+
ui_thread.print_verbose(f"Running command (contains sensitive user secrets, do not paste outside this machine!):\n"
|
|
245
|
+
f"#########################\n {' '.join(command)}\n#########################\n")
|
|
246
|
+
|
|
243
247
|
PyntContainerRegistry.instance().register_container(self)
|
|
244
248
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
245
249
|
stdout, stderr = process.communicate()
|
|
@@ -250,6 +254,7 @@ class PyntContainerNative:
|
|
|
250
254
|
container_id = stdout.strip()
|
|
251
255
|
|
|
252
256
|
if process.returncode and process.returncode != 0:
|
|
257
|
+
ui_thread.print_verbose(f"Unable to perform docker run command, return code: {process.returncode}")
|
|
253
258
|
raise DockerNativeUnavailableException(f"Unable to perform docker run command, return code: {process.returncode}")
|
|
254
259
|
|
|
255
260
|
# Start log streaming in a separate thread
|
|
@@ -288,7 +293,7 @@ class PyntContainerNative:
|
|
|
288
293
|
for container_id in container_ids:
|
|
289
294
|
kill_command = ["docker", "kill", container_id]
|
|
290
295
|
remove_command = ["docker", "remove", container_id]
|
|
291
|
-
subprocess.run(kill_command,stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
296
|
+
subprocess.run(kill_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
292
297
|
subprocess.run(remove_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
293
298
|
if report_to_user:
|
|
294
299
|
ui_thread.print(
|
|
@@ -303,12 +308,14 @@ class PyntContainerNative:
|
|
|
303
308
|
ui_thread.print(ui_thread.PrinterText("Pulling latest docker image", ui_thread.PrinterText.INFO))
|
|
304
309
|
pull_command = ['docker', 'pull', self.config.image.name]
|
|
305
310
|
get_image_command = ['docker', 'images', '-q', f'{self.config.image.name}']
|
|
311
|
+
ui_thread.print_verbose(f"Docker pull command:\n{' '.join(pull_command)}")
|
|
306
312
|
pull_process = subprocess.Popen(pull_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
307
313
|
_, pull_stderr = pull_process.communicate()
|
|
308
314
|
get_process = subprocess.Popen(get_image_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
309
315
|
get_stdout, _ = get_process.communicate()
|
|
310
316
|
local_image_id = get_stdout.decode('utf-8')
|
|
311
317
|
if self.config.image.is_self_managed and local_image_id:
|
|
318
|
+
ui_thread.print_verbose(f"Using local image {local_image_id}")
|
|
312
319
|
return local_image_id.strip()
|
|
313
320
|
elif local_image_id == "":
|
|
314
321
|
ui_thread.print(ui_thread.PrinterText(f"Error: the image {self.config.image.name} not found",
|
|
@@ -320,6 +327,9 @@ class PyntContainerNative:
|
|
|
320
327
|
|
|
321
328
|
if pull_process.returncode != 0:
|
|
322
329
|
raise ImageUnavailableException("Failed to pull image")
|
|
330
|
+
|
|
331
|
+
ui_thread.print_verbose("Image pulled successfully")
|
|
332
|
+
|
|
323
333
|
except Exception as e:
|
|
324
334
|
raise ImageUnavailableException(f"An error occurred: {str(e)}")
|
|
325
335
|
|
|
@@ -342,9 +352,13 @@ class PyntContainerNative:
|
|
|
342
352
|
def pre_run_validation(self, port):
|
|
343
353
|
self.kill_other_instances()
|
|
344
354
|
|
|
355
|
+
ui_thread.print_verbose("Checking if port is in use")
|
|
345
356
|
if container_utils.is_port_in_use(int(port)):
|
|
357
|
+
ui_thread.print_verbose(f"Port {port} is in use")
|
|
346
358
|
raise PortInUseException(port)
|
|
347
359
|
|
|
360
|
+
ui_thread.print_verbose("Port is not in use")
|
|
361
|
+
|
|
348
362
|
|
|
349
363
|
class PyntContainerRegistry:
|
|
350
364
|
_instance = None
|
|
@@ -360,8 +374,10 @@ class PyntContainerRegistry:
|
|
|
360
374
|
return PyntContainerRegistry._instance
|
|
361
375
|
|
|
362
376
|
def register_container(self, c: PyntContainerNative):
|
|
377
|
+
ui_thread.print_verbose("Registering container")
|
|
363
378
|
self.containers.append(c)
|
|
364
379
|
|
|
365
380
|
def stop_all_containers(self):
|
|
381
|
+
ui_thread.print_verbose("Stopping all containers")
|
|
366
382
|
for c in self.containers:
|
|
367
383
|
c.stop()
|
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
ignoreTests/conftest.py,sha256=gToq5K74GtgeGQXjFvXSzMaE6axBYxAzcFG5XJPOXjI,427
|
|
2
2
|
ignoreTests/auth/login.py,sha256=KFlzWhXBAuwdi7GXf16gCB3ya94LQG2wjcSChE149rQ,3798
|
|
3
3
|
ignoreTests/store/cred_store.py,sha256=_7-917EtNC9eKEumO2_lt-7KuDmCwOZFaowCm7DbA_A,254
|
|
4
|
-
pyntcli/__init__.py,sha256=
|
|
4
|
+
pyntcli/__init__.py,sha256=IdGt47yTByl81nIAWmL37jegJ46MzryO0ztrSBIalyA,24
|
|
5
5
|
pyntcli/main.py,sha256=RD0W2_0ogYBCXubo-YewxHYkiIXxNv6NkZOh3n1VujE,5964
|
|
6
6
|
pyntcli/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
pyntcli/analytics/send.py,sha256=0hJ0WJNFHLqyohtRr_xOg5WEXzxHrUOlcePPg-k65Hk,3846
|
|
8
8
|
pyntcli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
pyntcli/auth/login.py,sha256=TljsRXbEkNI1YUrKm5mlTw4YiecYScYUsit8Z8vstss,5228
|
|
10
10
|
pyntcli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
pyntcli/commands/burp.py,sha256=
|
|
12
|
-
pyntcli/commands/command.py,sha256=
|
|
11
|
+
pyntcli/commands/burp.py,sha256=sR5D2vlOXj9Qae3c1-VWfIXMouXoEv2Ik3O-C-Wqqkc,13891
|
|
12
|
+
pyntcli/commands/command.py,sha256=mMf1_bU7IFbE54FN3S3FuPODjHSSSu8aHzHJX-MgEFA,10832
|
|
13
13
|
pyntcli/commands/har.py,sha256=v6yWTuxPpXLlEi3yJ7gUDhFFGJZSpNQ3ehl2oaZJ-wc,4202
|
|
14
14
|
pyntcli/commands/id_command.py,sha256=UBEgMIpm4vauTCsKyixltiGUolNg_OfHEJvJ_i5BpJY,943
|
|
15
|
-
pyntcli/commands/listen.py,sha256=
|
|
16
|
-
pyntcli/commands/newman.py,sha256=
|
|
15
|
+
pyntcli/commands/listen.py,sha256=bsO7c3cHmZyxUTXspCcq_O9BR8tI2qYqOpHtJDb8hZY,8827
|
|
16
|
+
pyntcli/commands/newman.py,sha256=68P1bCjSSjTWrzOwKN_-fGQC4j484KlVpMbezMJOCMo,5045
|
|
17
17
|
pyntcli/commands/postman.py,sha256=ExfvG0iLRmK_rHcqIj8itcd3mh2-BdPuQEDm8viLP6Q,4891
|
|
18
|
-
pyntcli/commands/pynt_cmd.py,sha256=
|
|
18
|
+
pyntcli/commands/pynt_cmd.py,sha256=AeXxKy8dtZTdwWhTirHVvkPkeFUSoZNb3FRyuwkL6kg,7103
|
|
19
19
|
pyntcli/commands/root.py,sha256=UtGky7LfbfQ6mELHoNJcdHznFrUVBlhXI47_8QdApfc,4068
|
|
20
20
|
pyntcli/commands/static_file_extensions.py,sha256=PZJb02BI-64tbU-j3rdCNsXzTh7gkIDGxGKbKNw3h5k,1995
|
|
21
21
|
pyntcli/commands/sub_command.py,sha256=GF3-rE_qk2L4jGPFqHLm9SdGINmu3EakhjJTFyWjRms,374
|
|
22
|
-
pyntcli/commands/
|
|
22
|
+
pyntcli/commands/template.py,sha256=KocIH1T7occrrjaWJQaKKGgGWrU1bieTKEJbhFw3KLI,7976
|
|
23
|
+
pyntcli/commands/util.py,sha256=ev06aUlJ9tCSw1eAGVIsGlVVY4u0H6AJG9b5MB_bAzs,4594
|
|
23
24
|
pyntcli/log/__init__.py,sha256=cOGwOYzMoshEbZiiasBGkj6wF0SBu3Jdpl-AuakDesw,19
|
|
24
25
|
pyntcli/log/log.py,sha256=YXCvcCzuhQ5QUT2L02uQEdN_lTCzLEuet4OnLuEnjlM,112
|
|
25
26
|
pyntcli/pynt_docker/__init__.py,sha256=PQIOVxc7XXtMLfEX7ojgwf_Z3mmTllO3ZvzUZTPOxQY,30
|
|
26
27
|
pyntcli/pynt_docker/container_utils.py,sha256=_Onn7loInzyJAG2-Uk6CGpsuRyelmUFHOvtJj4Uzi9A,175
|
|
27
|
-
pyntcli/pynt_docker/pynt_container.py,sha256=
|
|
28
|
+
pyntcli/pynt_docker/pynt_container.py,sha256=c86YmbPMzj-LIkwRikNs3vXLL4uV0qrxIqVrcQ8Ayd4,14103
|
|
28
29
|
pyntcli/store/__init__.py,sha256=1fP8cEAQCF_myja3gnhHH9FEqtBiOJ-2aBmUXSKBdFA,41
|
|
29
30
|
pyntcli/store/json_connector.py,sha256=UGs3uORw3iyn0YJ8kzab-veEZToA6d-ByXYuqEleWsA,560
|
|
30
31
|
pyntcli/store/store.py,sha256=Kl_HT9FJFdKlAH7SwAt3g4-bW-r-1ve_u13OPggQai0,2529
|
|
@@ -39,8 +40,8 @@ pyntcli/ui/report.py,sha256=W-icPSZrGLOubXgam0LpOvHLl_aZg9Zx9qIkL8Ym5PE,1930
|
|
|
39
40
|
pyntcli/ui/ui_thread.py,sha256=XUBgLpYQjVhrilU-ofw7VSXgTiwneSdTxm61EvC3x4Q,5091
|
|
40
41
|
tests/test_utils.py,sha256=t5fTQUk1U_Js6iMxcGYGqt4C-crzOJ0CqCKtLkRtUi0,2050
|
|
41
42
|
tests/commands/test_pynt_cmd.py,sha256=BjGFCFACcSziLrNA6_27t6TjSmvdu54wx9njwLpRSJY,8379
|
|
42
|
-
pyntcli-0.1.
|
|
43
|
-
pyntcli-0.1.
|
|
44
|
-
pyntcli-0.1.
|
|
45
|
-
pyntcli-0.1.
|
|
46
|
-
pyntcli-0.1.
|
|
43
|
+
pyntcli-0.1.110.dist-info/METADATA,sha256=rIqnBOeMS4-WHejM5GIJkVjygadDPYKPJBzhDsLhqXk,428
|
|
44
|
+
pyntcli-0.1.110.dist-info/WHEEL,sha256=a7TGlA-5DaHMRrarXjVbQagU3Man_dCnGIWMJr5kRWo,91
|
|
45
|
+
pyntcli-0.1.110.dist-info/entry_points.txt,sha256=kcGmqAxXDttNk2EPRcqunc_LTVp61gzakz0v-GEE2SY,43
|
|
46
|
+
pyntcli-0.1.110.dist-info/top_level.txt,sha256=64XSgBzSpgwjYjEKHZE7q3JH2a816zEeyZBXfJi3AKI,42
|
|
47
|
+
pyntcli-0.1.110.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|