pyntcli 0.1.73__py3-none-any.whl → 0.1.75__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.
@@ -1,5 +1,5 @@
1
1
  import argparse
2
- import time
2
+ import time
3
3
  import websocket
4
4
  import webbrowser
5
5
  import os
@@ -14,28 +14,31 @@ from pyntcli.pynt_docker import pynt_container
14
14
  from pyntcli.ui import ui_thread
15
15
  from pyntcli.transport import pynt_requests
16
16
 
17
+
17
18
  class PyntPostmanException(Exception):
18
19
  pass
19
20
 
21
+
20
22
  class PyntWebSocketException(PyntPostmanException):
21
23
  pass
22
24
 
25
+
23
26
  logger = log.get_logger()
24
27
 
28
+
25
29
  def postman_usage():
26
30
  return ui_thread.PrinterText("Integration with postman, run scan from pynt postman collection") \
27
31
  .with_line("") \
28
- .with_line("Usage:",style=ui_thread.PrinterText.HEADER) \
32
+ .with_line("Usage:", style=ui_thread.PrinterText.HEADER) \
29
33
  .with_line("\tpynt postman [OPTIONS]") \
30
34
  .with_line("") \
31
- .with_line("Options:",style=ui_thread.PrinterText.HEADER) \
35
+ .with_line("Options:", style=ui_thread.PrinterText.HEADER) \
32
36
  .with_line("\t--port - set the port pynt will listen to (DEFAULT: 5001)") \
33
37
  .with_line("\t--insecure - use when target uses self signed certificates") \
34
38
  .with_line("\t--host-ca - path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN.")
35
39
 
36
40
 
37
-
38
- class PostmanSubCommand(sub_command.PyntSubCommand):
41
+ class PostmanSubCommand(sub_command.PyntSubCommand):
39
42
  def __init__(self, name) -> None:
40
43
  super().__init__(name)
41
44
  self.server_base_url = "http://localhost:{}/api"
@@ -43,18 +46,17 @@ class PostmanSubCommand(sub_command.PyntSubCommand):
43
46
  def usage(self, *args):
44
47
  ui_thread.print(postman_usage())
45
48
 
46
- def add_cmd(self, parent_command: argparse._SubParsersAction) -> argparse.ArgumentParser:
49
+ def add_cmd(self, parent_command: argparse._SubParsersAction) -> argparse.ArgumentParser:
47
50
  postman_cmd = parent_command.add_parser(self.name)
48
51
  postman_cmd.add_argument("--port", "-p", help="set the port pynt will listen to (DEFAULT: 5001)", type=int, default=5001)
49
52
  postman_cmd.print_usage = self.usage
50
53
  postman_cmd.print_help = self.usage
51
54
  return postman_cmd
52
-
53
55
 
54
56
  def scan_id_generator(self, port):
55
57
  try:
56
58
  ws = websocket.WebSocket()
57
- ws.connect("ws://localhost:{}/api/scan_id".format(port))
59
+ ws.connect("ws://localhost:{}/api/scan_id".format(port))
58
60
 
59
61
  while ws.connected:
60
62
  scan_id = ws.recv()
@@ -69,7 +71,7 @@ class PostmanSubCommand(sub_command.PyntSubCommand):
69
71
  finally:
70
72
  ws.close()
71
73
 
72
- def get_report(self, port,report_format, scan_id):
74
+ def get_report(self, port, report_format, scan_id):
73
75
  while True:
74
76
  res = pynt_requests.get(self.server_base_url.format(port) + "/report?format={}".format(report_format), params={"scanId": scan_id})
75
77
  if res.status_code == HTTPStatus.OK:
@@ -79,42 +81,38 @@ class PostmanSubCommand(sub_command.PyntSubCommand):
79
81
  continue
80
82
  if res.status_code == HTTPStatus.BAD_REQUEST:
81
83
  return
82
- if res.status_code == 517: #pynt did not recieve any requests
84
+ if res.status_code == 517: # pynt did not recieve any requests
83
85
  ui_thread.print(ui_thread.PrinterText(res.json()["message"], ui_thread.PrinterText.WARNING))
84
- return
86
+ return
85
87
  ui_thread.print("Error in polling for scan report: {}".format(res.text))
86
- return
87
-
88
+ return
88
89
 
89
90
  def run_cmd(self, args: argparse.Namespace):
90
- if "application_id" in args and args.application_id:
91
- ui_thread.print("application-id is not supported in postman integration, use the request body in the start scan request")
91
+ if "application_id" in args and args.application_id:
92
+ ui_thread.print("application-id is not supported in postman integration, use the collection variables to set application id.")
92
93
  args.application_id = ""
93
94
 
94
- container = pynt_container.get_container_with_arguments(args ,pynt_container.PyntDockerPort("5001", args.port, name="--port"))
95
-
96
- if util.is_port_in_use(args.port):
97
- ui_thread.print(ui_thread.PrinterText("Port: {} already in use, please use a different one".format(args.port), ui_thread.PrinterText.WARNING))
98
- return
95
+ container = pynt_container.get_container_with_arguments(args, pynt_container.PyntDockerPort("5001", args.port, name="--port"))
99
96
 
100
- postman_docker = pynt_container.PyntContainer(image_name=pynt_container.PYNT_DOCKER_IMAGE,
101
- tag="postman-latest",
102
- detach=True,
103
- base_container=container)
97
+
98
+ postman_docker = pynt_container.PyntContainer(image_name=pynt_container.PYNT_DOCKER_IMAGE,
99
+ tag="postman-latest",
100
+ detach=True,
101
+ base_container=container)
104
102
 
105
103
  postman_docker.run()
106
- ui_thread.print_generator(postman_docker.stdout)
107
-
104
+ ui_thread.print_generator(postman_docker.stdout)
105
+
108
106
  util.wait_for_healthcheck("http://localhost:{}".format(args.port))
109
107
 
110
108
  for scan_id in self.scan_id_generator(args.port):
111
- html_report = self.get_report(args.port, "html",scan_id)
109
+ html_report = self.get_report(args.port, "html", scan_id)
112
110
  html_report_path = os.path.join(tempfile.gettempdir(), "pynt_report_{}.html".format(int(time.time())))
113
111
 
114
112
  if html_report:
115
113
  with open(html_report_path, "w", encoding="utf-8") as html_file:
116
114
  html_file.write(html_report)
117
115
  webbrowser.open("file://{}".format(html_report_path))
118
-
116
+
119
117
  if not postman_docker.is_alive():
120
118
  ui_thread.print(ui_thread.PrinterText("Pynt container is not available", ui_thread.PrinterText.WARNING))
@@ -5,6 +5,9 @@ from pyntcli.analytics import send as analytics
5
5
  from pyntcli.transport import pynt_requests
6
6
  from pyntcli.ui import ui_thread
7
7
 
8
+ from requests.exceptions import SSLError, HTTPError
9
+
10
+
8
11
  from . import command, listen, postman, root, sub_command, id_command, newman, har, burp
9
12
 
10
13
  avail_sub_commands = [
@@ -17,29 +20,40 @@ avail_sub_commands = [
17
20
  burp.BurpCommand("burp"),
18
21
  ]
19
22
 
23
+
20
24
  class PyntCommandException(Exception):
21
25
  pass
22
26
 
27
+
23
28
  class BadArgumentsException(PyntCommandException):
24
29
  pass
25
30
 
31
+
26
32
  class NoSuchCommandException(PyntCommandException):
27
33
  pass
28
34
 
29
35
 
30
36
  VERSION_CHECK_URL = "https://d1efigcr4c19qn.cloudfront.net/cli/version"
31
37
 
38
+
32
39
  def check_is_latest_version(current_version):
33
- res = pynt_requests.get(VERSION_CHECK_URL)
34
- res.raise_for_status()
35
40
 
36
- latest_versions = res.text.replace("\n","")
41
+ try:
42
+ res = pynt_requests.get(VERSION_CHECK_URL)
43
+ res.raise_for_status()
37
44
 
38
- if current_version != latest_versions:
39
- ui_thread.print(ui_thread.PrinterText("""Pynt CLI new version is available, upgrade now with:
40
- python3 -m pip install --upgrade pyntcli""",ui_thread.PrinterText.WARNING))
45
+ latest_versions = res.text.replace("\n", "")
41
46
 
42
- class PyntCommand:
47
+ if current_version != latest_versions:
48
+ ui_thread.print(ui_thread.PrinterText("""Pynt CLI new version is available, upgrade now with:
49
+ python3 -m pip install --upgrade pyntcli""", ui_thread.PrinterText.WARNING))
50
+ except SSLError:
51
+ ui_thread.print(ui_thread.PrinterText("""Error: Unable to check if Pynt CLI version is up-to-date due to VPN/proxy. Run Pynt with --insecure to fix.""", ui_thread.PrinterText.WARNING))
52
+ except HTTPError:
53
+ ui_thread.print("""Unable to check if Pynt CLI version is up-to-date""")
54
+
55
+
56
+ class PyntCommand:
43
57
  def __init__(self) -> None:
44
58
  self.base: root.BaseCommand = root.BaseCommand()
45
59
  self.sub_commands: Dict[str, sub_command.PyntSubCommand] = {sc.get_name(): sc for sc in avail_sub_commands}
@@ -50,25 +64,25 @@ class PyntCommand:
50
64
  for sc in self.sub_commands.values():
51
65
  self.base.add_base_arguments(sc.add_cmd(self.base.get_subparser()))
52
66
 
53
- def parse_args(self, args_from_cmd: List[str]):
67
+ def parse_args(self, args_from_cmd: List[str]):
54
68
  return self.base.cmd().parse_args(args_from_cmd)
55
69
 
56
70
  def run_cmd(self, args: argparse.Namespace):
57
71
  if not "command" in args:
58
72
  raise BadArgumentsException()
59
-
60
- command = getattr(args, "command")
73
+
74
+ command = getattr(args, "command")
61
75
  if not command in self.sub_commands:
62
76
  raise NoSuchCommandException()
63
-
77
+
64
78
  if "host_ca" in args and args.host_ca:
65
79
  pynt_requests.add_host_ca(args.host_ca)
66
-
80
+
67
81
  if "insecure" in args and args.insecure:
68
82
  pynt_requests.disable_tls_termination()
69
83
 
70
84
  check_is_latest_version(cli_version)
71
85
  analytics.emit(analytics.CLI_START)
72
-
86
+
73
87
  self.base.run_cmd(args)
74
88
  self.sub_commands[command].run_cmd(args)
pyntcli/commands/root.py CHANGED
@@ -4,6 +4,7 @@ import sys
4
4
  from os import environ
5
5
 
6
6
  from pyntcli.auth import login
7
+ import pyntcli.log.log as log
7
8
  from pyntcli.ui import ui_thread
8
9
  from pyntcli.analytics import send as analytics
9
10
 
@@ -94,5 +95,6 @@ class BaseCommand:
94
95
  user_id = login.user_id()
95
96
  if user_id:
96
97
  analytics.set_user_id(user_id)
98
+ log.add_user_details(user_id)
97
99
 
98
100
  check_cicd_context()
pyntcli/commands/util.py CHANGED
@@ -15,79 +15,81 @@ from pyntcli.transport import pynt_requests
15
15
 
16
16
  logger = log.get_logger()
17
17
 
18
- def is_port_in_use(port: int) -> bool:
19
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
20
- return s.connect_ex(('localhost', port)) == 0
21
18
 
22
19
  def find_open_port() -> int:
23
20
  with socket.socket() as s:
24
- s.bind(('', 0))
25
- return s.getsockname()[1]
21
+ s.bind(('', 0))
22
+ return s.getsockname()[1]
23
+
26
24
 
27
25
  HEALTHCHECK_TIMEOUT = 60
28
26
  HEALTHCHECK_INTERVAL = 0.1
29
27
 
30
- def wait_for_healthcheck(address):
28
+
29
+ def wait_for_healthcheck(address):
31
30
  start = time.time()
32
- while start + HEALTHCHECK_TIMEOUT > time.time():
31
+ while start + HEALTHCHECK_TIMEOUT > time.time():
33
32
  try:
34
33
  res = pynt_requests.get(address + "/healthcheck")
35
34
  if res.status_code == 418:
36
- return
37
- except:
35
+ return
36
+ except:
38
37
  time.sleep(HEALTHCHECK_INTERVAL)
39
38
 
40
39
  logger.debug("Health check timed out!")
41
40
  raise TimeoutError()
42
41
 
43
- def get_user_report_path(path,file_type):
42
+
43
+ def get_user_report_path(path, file_type):
44
44
  path = Path(path)
45
45
  if path.is_dir():
46
- return os.path.join(path, "pynt_results_{}.{}".format(int(time.time()),file_type))
47
-
46
+ return os.path.join(path, "pynt_results_{}.{}".format(int(time.time()), file_type))
47
+
48
48
  return os.path.join(str(path.parent), path.stem + ".{}".format(file_type))
49
-
49
+
50
50
 
51
51
  class HtmlReportNotCreatedException(Exception):
52
52
  pass
53
53
 
54
- class SomeFoundingsOrWargningsException(Exception):
54
+
55
+ class SomeFindingsOrWarningsException(Exception):
55
56
  pass
56
57
 
58
+
57
59
  @contextmanager
58
60
  def create_default_file_mounts(args):
59
61
  html_report_path = os.path.join(tempfile.gettempdir(), "results.html")
60
62
  json_report_path = os.path.join(tempfile.gettempdir(), "results.json")
61
63
 
62
- if "reporters" in args and args.reporters:
64
+ if "reporters" in args and args.reporters:
63
65
  html_report_path = os.path.join(os.getcwd(), "pynt_results.html")
64
66
  json_report_path = os.path.join(os.getcwd(), "pynt_results.json")
65
67
 
66
68
  mounts = []
67
- with open(html_report_path, "w"), open(json_report_path, "w"):
69
+ with open(html_report_path, "w"), open(json_report_path, "w"):
68
70
  mounts.append(pynt_container.create_mount(json_report_path, "/etc/pynt/results/results.json"))
69
71
  mounts.append(pynt_container.create_mount(html_report_path, "/etc/pynt/results/results.html"))
70
-
72
+
71
73
  yield mounts
72
-
74
+
73
75
  if os.stat(html_report_path).st_size == 0:
74
76
  raise HtmlReportNotCreatedException()
75
-
77
+
76
78
  webbrowser.open("file://{}".format(html_report_path))
77
79
 
78
80
  if os.stat(html_report_path).st_size > 0:
79
81
  report.PyntReporter(json_report_path).print_summary()
80
-
82
+
81
83
  check_for_findings_or_warnings(args, json.load(open(json_report_path)))
82
84
 
85
+
83
86
  def check_for_findings_or_warnings(args, json_report):
84
- security_tests = json_report.get("securityTests",{})
87
+ security_tests = json_report.get("securityTests", {})
85
88
  findings = security_tests.get("Findings", 0)
86
89
  warnings = security_tests.get("Warnings", 0)
87
90
 
88
91
  if "return_error" in args and args.return_error != "never" and findings != 0:
89
- raise SomeFoundingsOrWargningsException()
92
+ raise SomeFindingsOrWarningsException()
90
93
 
91
94
  if "return_error" in args and args.return_error == "all-findings" and warnings != 0:
92
- raise SomeFoundingsOrWargningsException()
93
-
95
+ raise SomeFindingsOrWarningsException()
pyntcli/log/log.py CHANGED
@@ -15,7 +15,8 @@ LOGGING = {
15
15
  'level': 'DEBUG',
16
16
  'token': "KOfjdWTcZXmjAwAOslpFYwhLpDzFTfJl",
17
17
  'logs_drain_timeout': 5,
18
- 'url': 'https://listener.logz.io:8071'
18
+ 'url': 'https://listener.logz.io:8071',
19
+ 'retries_no': 1,
19
20
  }
20
21
  },
21
22
  'loggers': {
pyntcli/main.py CHANGED
@@ -10,9 +10,10 @@ from pyntcli.ui import ui_thread
10
10
  from pyntcli.auth import login
11
11
  from pyntcli.analytics import send as analytics
12
12
  from requests.exceptions import SSLError
13
+ from requests.exceptions import ProxyError
13
14
  from pyntcli.transport.pynt_requests import InvalidPathException, InvalidCertFormat
14
15
  from pyntcli.commands.util import HtmlReportNotCreatedException
15
- from pyntcli.commands.util import SomeFoundingsOrWargningsException
16
+ from pyntcli.commands.util import SomeFindingsOrWarningsException
16
17
  from pyntcli.commands.postman import PyntWebSocketException
17
18
  from pyntcli import __version__
18
19
 
@@ -45,6 +46,7 @@ def start_analytics():
45
46
  user_id = login.user_id()
46
47
  if user_id:
47
48
  analytics.set_user_id(user_id)
49
+ log.add_user_details(user_id)
48
50
 
49
51
 
50
52
  def main():
@@ -57,35 +59,41 @@ def main():
57
59
  cli = pynt_cmd.PyntCommand()
58
60
  cli.run_cmd(cli.parse_args(argv[1:]))
59
61
  analytics.stop()
60
- except pynt_cmd.PyntCommandException:
62
+ except pynt_cmd.PyntCommandException as e:
61
63
  pynt_cmd.root.usage()
62
- except pynt_container.DockerNotAvailableException:
64
+ except pynt_container.DockerNotAvailableException as e:
63
65
  ui_thread.print(ui_thread.PrinterText("Docker was unavailable, please make sure docker is installed and running.", ui_thread.PrinterText.WARNING))
64
- analytics.emit(analytics.ERROR, {"error": "docker unavailable"})
65
- except SSLError:
66
- ui_thread.print(ui_thread.PrinterText("We encountered SSL issues and could not proceed, this may be the cause of a VPN or a Firewall in place.", ui_thread.PrinterText.WARNING))
67
- except login.Timeout:
66
+ analytics.emit(analytics.ERROR, {"error": "docker unavailable. e: {}".format(e)})
67
+ except SSLError as e:
68
+ ui_thread.print(ui_thread.PrinterText("We encountered SSL issues and could not proceed, this may be the cause of a VPN or a Firewall in place. Run again with --insecure", ui_thread.PrinterText.WARNING))
69
+ analytics.emit(analytics.ERROR, {"error": "ssl error. e: {}".format(e)})
70
+ except login.Timeout as e:
68
71
  ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to incomplete registration, please try again.", ui_thread.PrinterText.WARNING))
69
- analytics.emit(analytics.ERROR, {"error": "login timeout"})
70
- except login.InvalidTokenInEnvVarsException:
72
+ analytics.emit(analytics.ERROR, {"error": "login timeout. e: {}".format(e)})
73
+ except login.InvalidTokenInEnvVarsException as e:
71
74
  ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to malformed credentials provided in env vars.", ui_thread.PrinterText.WARNING))
72
- analytics.emit(analytics.ERROR, {"error": "invalid pynt cli credentials in env vars"})
73
- except pynt_container.ImageUnavailableException:
74
- analytics.emit(analytics.ERROR, {"error": "Couldnt pull pynt image and no local image found"})
75
- pynt_errors.unexpected_error()
76
- except HtmlReportNotCreatedException:
77
- analytics.emit(analytics.ERROR, {"error": "Html report was not created"})
78
- pynt_errors.unexpected_error()
75
+ analytics.emit(analytics.ERROR, {"error": "invalid pynt cli credentials in env vars. e: {}".format(e)})
76
+ except pynt_container.ImageUnavailableException as e:
77
+ analytics.emit(analytics.ERROR, {"error": "Couldn't pull pynt image and no local image found. e: {}".format(e)})
78
+ ui_thread.print(ui_thread.PrinterText("Error: Couldn't pull pynt image and no local image found.", ui_thread.PrinterText.WARNING))
79
+ except HtmlReportNotCreatedException as e:
80
+ analytics.emit(analytics.ERROR, {"error": "Html report was not created. e: {}".format(e)})
81
+ ui_thread.print(ui_thread.PrinterText("Pynt CLI exited: Html report was not created.", ui_thread.PrinterText.WARNING))
79
82
  except InvalidPathException as e:
80
83
  ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to invalid host-CA path: {}".format(e), ui_thread.PrinterText.WARNING))
81
- analytics.emit(analytics.ERROR, {"error": "Host CA path provided was invalid"})
84
+ analytics.emit(analytics.ERROR, {"error": "Host CA path provided was invalid. e: {}".format(e)})
82
85
  except InvalidCertFormat as e:
83
86
  ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to invalid host-CA. Please provide a file in PEM format: {}".format(e), ui_thread.PrinterText.WARNING))
84
- analytics.emit(analytics.ERROR, {"error": "Host CA provided was not in valid pem format"})
85
- except PyntWebSocketException:
86
- analytics.emit(analytics.ERROR, {"error": "postman websocket failed to connect"})
87
- pynt_errors.unexpected_error()
88
- except SomeFoundingsOrWargningsException as e:
87
+ analytics.emit(analytics.ERROR, {"error": "Host CA provided was not in valid pem format. e: {}".format(e)})
88
+ except PyntWebSocketException as e:
89
+ analytics.emit(analytics.ERROR, {"error": "postman websocket failed to connect. e: {}".format(e)})
90
+ ui_thread.print(ui_thread.PrinterText("Pynt CLI exited: postman websocket failed to connect", ui_thread.PrinterText.WARNING))
91
+ except ProxyError as e:
92
+ ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to a proxy error. check if your proxy is up", ui_thread.PrinterText.WARNING))
93
+ analytics.emit(analytics.ERROR, {"error": "there was a proxy error. e: {}".format(e)})
94
+ except pynt_container.PortInUseException as e:
95
+ analytics.emit(analytics.ERROR, {"error": "port in use. e: {}".format(e)})
96
+ except SomeFindingsOrWarningsException as e:
89
97
  exit(1)
90
98
  except Exception as e:
91
99
  analytics.emit(analytics.ERROR, {"error": "{}".format(e)})
@@ -0,0 +1,7 @@
1
+
2
+ import socket
3
+
4
+
5
+ def is_port_in_use(port: int) -> bool:
6
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
7
+ return s.connect_ex(('localhost', port)) == 0