pyntcli 0.1.29__py3-none-any.whl → 0.1.31__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.
- build/lib/build/lib/build/lib/pyntcli/__init__.py +1 -0
- build/lib/build/lib/build/lib/pyntcli/analytics/__init__.py +0 -0
- build/lib/build/lib/build/lib/pyntcli/analytics/send.py +70 -0
- build/lib/build/lib/build/lib/pyntcli/auth/__init__.py +0 -0
- build/lib/build/lib/build/lib/pyntcli/auth/login.py +156 -0
- build/lib/build/lib/build/lib/pyntcli/commands/__init__.py +0 -0
- build/lib/build/lib/build/lib/pyntcli/commands/har.py +68 -0
- build/lib/build/lib/build/lib/pyntcli/commands/id_command.py +28 -0
- build/lib/build/lib/build/lib/pyntcli/commands/newman.py +85 -0
- build/lib/build/lib/build/lib/pyntcli/commands/postman.py +58 -0
- build/lib/build/lib/build/lib/pyntcli/commands/proxy.py +145 -0
- build/lib/build/lib/build/lib/pyntcli/commands/pynt_cmd.py +47 -0
- build/lib/build/lib/build/lib/pyntcli/commands/root.py +57 -0
- build/lib/build/lib/build/lib/pyntcli/commands/sub_command.py +15 -0
- build/lib/build/lib/build/lib/pyntcli/commands/util.py +26 -0
- build/lib/build/lib/build/lib/pyntcli/main.py +77 -0
- build/lib/build/lib/build/lib/pyntcli/pynt_docker/__init__.py +1 -0
- build/lib/build/lib/build/lib/pyntcli/pynt_docker/pynt_container.py +178 -0
- build/lib/build/lib/build/lib/pyntcli/store/__init__.py +1 -0
- build/lib/build/lib/build/lib/pyntcli/store/json_connector.py +23 -0
- build/lib/build/lib/build/lib/pyntcli/store/store.py +55 -0
- build/lib/build/lib/build/lib/pyntcli/store/store_connector.py +17 -0
- build/lib/build/lib/build/lib/pyntcli/ui/__init__.py +0 -0
- build/lib/build/lib/build/lib/pyntcli/ui/progress.py +31 -0
- build/lib/build/lib/build/lib/pyntcli/ui/ui_thread.py +168 -0
- build/lib/build/lib/build/lib/tests/auth/test_login.py +96 -0
- build/lib/build/lib/build/lib/tests/conftest.py +24 -0
- build/lib/build/lib/build/lib/tests/store/test_cred_store.py +11 -0
- build/lib/build/lib/pyntcli/__init__.py +1 -0
- build/lib/build/lib/pyntcli/analytics/__init__.py +0 -0
- build/lib/build/lib/pyntcli/analytics/send.py +70 -0
- build/lib/build/lib/pyntcli/auth/__init__.py +0 -0
- build/lib/build/lib/pyntcli/auth/login.py +156 -0
- build/lib/build/lib/pyntcli/commands/__init__.py +0 -0
- build/lib/build/lib/pyntcli/commands/har.py +68 -0
- build/lib/build/lib/pyntcli/commands/id_command.py +28 -0
- build/lib/build/lib/pyntcli/commands/newman.py +85 -0
- build/lib/build/lib/pyntcli/commands/postman.py +58 -0
- build/lib/build/lib/pyntcli/commands/proxy.py +147 -0
- build/lib/build/lib/pyntcli/commands/pynt_cmd.py +47 -0
- build/lib/build/lib/pyntcli/commands/root.py +57 -0
- build/lib/build/lib/pyntcli/commands/sub_command.py +15 -0
- build/lib/build/lib/pyntcli/commands/util.py +26 -0
- build/lib/build/lib/pyntcli/main.py +77 -0
- build/lib/build/lib/pyntcli/pynt_docker/__init__.py +1 -0
- build/lib/build/lib/pyntcli/pynt_docker/pynt_container.py +178 -0
- build/lib/build/lib/pyntcli/store/__init__.py +1 -0
- build/lib/build/lib/pyntcli/store/json_connector.py +23 -0
- build/lib/build/lib/pyntcli/store/store.py +55 -0
- build/lib/build/lib/pyntcli/store/store_connector.py +17 -0
- build/lib/build/lib/pyntcli/ui/__init__.py +0 -0
- build/lib/build/lib/pyntcli/ui/progress.py +31 -0
- build/lib/build/lib/pyntcli/ui/ui_thread.py +168 -0
- build/lib/build/lib/tests/auth/test_login.py +96 -0
- build/lib/build/lib/tests/conftest.py +24 -0
- build/lib/build/lib/tests/store/test_cred_store.py +11 -0
- build/lib/pyntcli/__init__.py +1 -1
- build/lib/pyntcli/commands/proxy.py +1 -0
- pyntcli/__init__.py +1 -1
- pyntcli/commands/proxy.py +0 -2
- {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/METADATA +1 -1
- pyntcli-0.1.31.dist-info/RECORD +117 -0
- pyntcli-0.1.29.dist-info/RECORD +0 -61
- {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/WHEEL +0 -0
- {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/entry_points.txt +0 -0
- {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.31"
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import time
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from pyntcli import __version__
|
|
6
|
+
|
|
7
|
+
PYNT_DEFAULT_USER_ID = "d9e3b82b-2900-43bf-8c8f-7ffe2f0cda36"
|
|
8
|
+
MIXPANEL_TOKEN = "05c26edb86084bbbb803eed6818cd8aa"
|
|
9
|
+
MIXPANEL_URL = "https://api-eu.mixpanel.com/track?ip=1"
|
|
10
|
+
|
|
11
|
+
def stop():
|
|
12
|
+
if not AnalyticsSender._instance:
|
|
13
|
+
return
|
|
14
|
+
AnalyticsSender.instance().done()
|
|
15
|
+
|
|
16
|
+
def emit(event, properties=None):
|
|
17
|
+
AnalyticsSender.instance().emit(event, properties)
|
|
18
|
+
|
|
19
|
+
def set_user_id(user_id):
|
|
20
|
+
AnalyticsSender.instance().set_user_id(user_id)
|
|
21
|
+
|
|
22
|
+
CLI_START = "cli_start"
|
|
23
|
+
LOGIN_START = "cli_login_start"
|
|
24
|
+
LOGIN_DONE = "cli_login_done"
|
|
25
|
+
ERROR = "error"
|
|
26
|
+
|
|
27
|
+
class AnalyticsSender():
|
|
28
|
+
_instance = None
|
|
29
|
+
|
|
30
|
+
def __init__(self, user_id=PYNT_DEFAULT_USER_ID) -> None:
|
|
31
|
+
self.user_id = user_id
|
|
32
|
+
self.version = __version__
|
|
33
|
+
self.events = []
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def instance():
|
|
37
|
+
if not AnalyticsSender._instance:
|
|
38
|
+
AnalyticsSender._instance = AnalyticsSender()
|
|
39
|
+
|
|
40
|
+
return AnalyticsSender._instance
|
|
41
|
+
|
|
42
|
+
def base_event(self, event_type):
|
|
43
|
+
return {
|
|
44
|
+
"event": event_type,
|
|
45
|
+
"properties": {
|
|
46
|
+
"time": time.time(),
|
|
47
|
+
"distinct_id": self.user_id,
|
|
48
|
+
"$os": platform.platform(),
|
|
49
|
+
"cli_version": self.version,
|
|
50
|
+
"token": MIXPANEL_TOKEN
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def emit(self, event, properties):
|
|
55
|
+
base_event = self.base_event(event)
|
|
56
|
+
|
|
57
|
+
if properties:
|
|
58
|
+
for k,v in properties.items():
|
|
59
|
+
base_event["properties"][k] = v
|
|
60
|
+
|
|
61
|
+
self.events.append(base_event)
|
|
62
|
+
|
|
63
|
+
def set_user_id(self, user_id):
|
|
64
|
+
self.user_id = user_id
|
|
65
|
+
for i, _ in enumerate(self.events):
|
|
66
|
+
self.events[i]["properties"]["distinct_id"] = user_id
|
|
67
|
+
|
|
68
|
+
def done(self):
|
|
69
|
+
requests.post(MIXPANEL_URL, json=self.events)
|
|
70
|
+
self.events = []
|
|
File without changes
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from base64 import b64decode
|
|
2
|
+
import requests
|
|
3
|
+
import webbrowser
|
|
4
|
+
import uuid
|
|
5
|
+
import urllib.parse
|
|
6
|
+
import datetime
|
|
7
|
+
import time
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from pyntcli.ui import ui_thread
|
|
12
|
+
from pyntcli.store import CredStore
|
|
13
|
+
|
|
14
|
+
class LoginException(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class Timeout(LoginException):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class InvalidTokenInEnvVarsException(LoginException):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
PYNT_CREDENTIALS = "PYNT_CREDENTIALS"
|
|
24
|
+
|
|
25
|
+
class Login():
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self.delay = 5
|
|
28
|
+
self.base_authorization_url = "https://pynt.io/login?"
|
|
29
|
+
self.poll_url = "https://n592meacjj.execute-api.us-east-1.amazonaws.com/default/cli_validate_login"
|
|
30
|
+
self.login_wait_period = (60 *3) #3 minutes
|
|
31
|
+
|
|
32
|
+
def create_login_request(self):
|
|
33
|
+
request_id = uuid.uuid4()
|
|
34
|
+
request_url = self.base_authorization_url + urllib.parse.urlencode({"request_id": request_id, "utm_source": "cli"})
|
|
35
|
+
webbrowser.open(request_url)
|
|
36
|
+
|
|
37
|
+
ui_thread.print(ui_thread.PrinterText("To continue, you need to log in to your account.")\
|
|
38
|
+
.with_line("You will now be redirected to the login page.") \
|
|
39
|
+
.with_line("") \
|
|
40
|
+
.with_line("If you are not automatically redirected, please click on the link provided below (or copy to your web browser)") \
|
|
41
|
+
.with_line(request_url))
|
|
42
|
+
return request_id
|
|
43
|
+
|
|
44
|
+
def get_token_using_request_id(self, request_id):
|
|
45
|
+
with ui_thread.spinner("Waiting...", "point"):
|
|
46
|
+
start = time.time()
|
|
47
|
+
while start + self.login_wait_period > time.time():
|
|
48
|
+
response = requests.get(self.poll_url, params={"request_id": request_id})
|
|
49
|
+
if response.status_code == 200:
|
|
50
|
+
return response.json()
|
|
51
|
+
time.sleep(self.delay)
|
|
52
|
+
raise Timeout()
|
|
53
|
+
|
|
54
|
+
def login(self):
|
|
55
|
+
id = self.create_login_request()
|
|
56
|
+
token = self.get_token_using_request_id(id)
|
|
57
|
+
with CredStore() as store:
|
|
58
|
+
store.put("token", token)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def refresh_request(refresh_token):
|
|
62
|
+
return requests.post("https://auth.pynt.io/default/refresh", json={"refresh_token": refresh_token})
|
|
63
|
+
|
|
64
|
+
def refresh_token():
|
|
65
|
+
token = None
|
|
66
|
+
with CredStore() as store:
|
|
67
|
+
token = store.get("token")
|
|
68
|
+
|
|
69
|
+
if not token:
|
|
70
|
+
Login().login()
|
|
71
|
+
|
|
72
|
+
access_token = token.get("access_token")
|
|
73
|
+
if access_token and not is_jwt_expired(access_token):
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
refresh = token.get("refresh_token", None)
|
|
77
|
+
if not refresh:
|
|
78
|
+
Login().login()
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
refresh_response = refresh_request(refresh)
|
|
82
|
+
if refresh_response.status_code != 200:
|
|
83
|
+
Login().login()
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
with CredStore() as store:
|
|
87
|
+
token["access_token"] = refresh_response.json()["token"]
|
|
88
|
+
store.put("token", token)
|
|
89
|
+
|
|
90
|
+
def decode_jwt(jwt_token):
|
|
91
|
+
splited = jwt_token.split(".")
|
|
92
|
+
if len(splited) != 3:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
return json.loads(b64decode(splited[1] + '=' * (-len(splited[1]) % 4)))
|
|
96
|
+
|
|
97
|
+
def user_id():
|
|
98
|
+
with CredStore() as store:
|
|
99
|
+
token = store.get("token")
|
|
100
|
+
if not token:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
decoded = decode_jwt(token["access_token"])
|
|
104
|
+
if not decoded:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
return decoded.get("sub", None)
|
|
108
|
+
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
def is_jwt_expired(jwt_token):
|
|
112
|
+
decoded = decode_jwt(jwt_token)
|
|
113
|
+
if not decoded:
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
exp = decoded.get("exp", None)
|
|
117
|
+
if not exp:
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
return datetime.datetime.fromtimestamp(exp) < datetime.datetime.now() + datetime.timedelta(minutes=1)
|
|
121
|
+
|
|
122
|
+
def validate_creds_structure(data):
|
|
123
|
+
try:
|
|
124
|
+
creds = json.loads(data.replace("\n", ""))
|
|
125
|
+
token = creds.get("token", None)
|
|
126
|
+
if not token:
|
|
127
|
+
raise InvalidTokenInEnvVarsException()
|
|
128
|
+
if not isinstance(token, dict):
|
|
129
|
+
raise InvalidTokenInEnvVarsException()
|
|
130
|
+
|
|
131
|
+
refresh_token = token.get("refresh_token", None)
|
|
132
|
+
if not refresh_token:
|
|
133
|
+
raise InvalidTokenInEnvVarsException()
|
|
134
|
+
|
|
135
|
+
return token
|
|
136
|
+
except json.JSONDecodeError:
|
|
137
|
+
raise InvalidTokenInEnvVarsException()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def should_login():
|
|
141
|
+
env_creds = os.environ.get(PYNT_CREDENTIALS, None)
|
|
142
|
+
if env_creds:
|
|
143
|
+
validated_creds = validate_creds_structure(env_creds)
|
|
144
|
+
with CredStore() as store:
|
|
145
|
+
store.put("token", validated_creds)
|
|
146
|
+
|
|
147
|
+
with CredStore() as store:
|
|
148
|
+
token = store.get("token")
|
|
149
|
+
|
|
150
|
+
if not token or token == store.connector.default_value:
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
if not token.get("refresh_token"):
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import time
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from pyntcli.store.store import CredStore
|
|
6
|
+
from pyntcli.pynt_docker import pynt_container
|
|
7
|
+
from pyntcli.ui import ui_thread
|
|
8
|
+
from pyntcli.ui.progress import connect_progress_ws, wrap_ws_progress
|
|
9
|
+
from pyntcli.commands import sub_command, util
|
|
10
|
+
|
|
11
|
+
def har_usage():
|
|
12
|
+
return ui_thread.PrinterText("Integration with static har file testing") \
|
|
13
|
+
.with_line("") \
|
|
14
|
+
.with_line("Usage:", style=ui_thread.PrinterText.HEADER) \
|
|
15
|
+
.with_line("\tpynt har [OPTIONS]") \
|
|
16
|
+
.with_line("") \
|
|
17
|
+
.with_line("Options:", style=ui_thread.PrinterText.HEADER) \
|
|
18
|
+
.with_line("\t--har - Path to har file")
|
|
19
|
+
|
|
20
|
+
class HarSubCommand(sub_command.PyntSubCommand):
|
|
21
|
+
def __init__(self, name) -> None:
|
|
22
|
+
super().__init__(name)
|
|
23
|
+
|
|
24
|
+
def usage(self, *args):
|
|
25
|
+
ui_thread.print(har_usage())
|
|
26
|
+
|
|
27
|
+
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
28
|
+
har_cmd = parent.add_parser(self.name)
|
|
29
|
+
har_cmd.add_argument("--har", type=str, required=True)
|
|
30
|
+
har_cmd.print_usage = self.usage
|
|
31
|
+
har_cmd.print_help = self.usage
|
|
32
|
+
return har_cmd
|
|
33
|
+
|
|
34
|
+
def run_cmd(self, args: argparse.Namespace):
|
|
35
|
+
port = str(util.find_open_port())
|
|
36
|
+
docker_type , docker_arguments = pynt_container.get_container_with_arguments(pynt_container.PyntDockerPort(src=port, dest=port, name="--port"))
|
|
37
|
+
mounts = []
|
|
38
|
+
|
|
39
|
+
if not os.path.isfile(args.har):
|
|
40
|
+
ui_thread.print(ui_thread.PrinterText("Could not find the provided har path, please provide with a valid har path", ui_thread.PrinterText.WARNING))
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
har_name = os.path.basename(args.har)
|
|
44
|
+
docker_arguments += ["--har", har_name]
|
|
45
|
+
mounts.append(pynt_container.create_mount(os.path.abspath(args.har), "/etc/pynt/{}".format(har_name)))
|
|
46
|
+
|
|
47
|
+
mounts.append(pynt_container.create_mount(CredStore().get_path(), "/app/creds.json"))
|
|
48
|
+
|
|
49
|
+
if "insecure" in args and args.insecure:
|
|
50
|
+
docker_arguments.append("--insecure")
|
|
51
|
+
|
|
52
|
+
if "dev_flags" in args:
|
|
53
|
+
docker_arguments += args.dev_flags.split(" ")
|
|
54
|
+
|
|
55
|
+
har_docker = pynt_container.PyntContainer(image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
56
|
+
tag="har-latest",
|
|
57
|
+
detach=True,
|
|
58
|
+
mounts=mounts,
|
|
59
|
+
args=docker_arguments)
|
|
60
|
+
|
|
61
|
+
har_docker.run(docker_type)
|
|
62
|
+
|
|
63
|
+
util.wait_for_healthcheck("http://localhost:{}".format(port))
|
|
64
|
+
ui_thread.print_generator(ui_thread.AnsiText.wrap_gen(har_docker.stdout))
|
|
65
|
+
|
|
66
|
+
with ui_thread.progress(wrap_ws_progress(connect_progress_ws("ws://localhost:{}/progress".format(port))), "scan in progress..."):
|
|
67
|
+
while har_docker.is_alive():
|
|
68
|
+
time.sleep(1)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from pyntcli.store.store import CredStore
|
|
4
|
+
from pyntcli.commands import sub_command
|
|
5
|
+
from pyntcli.ui import ui_thread
|
|
6
|
+
|
|
7
|
+
def pyntid_usage():
|
|
8
|
+
return ui_thread.PrinterText("View your pynt-id to use when running pynt in CI pipeline") \
|
|
9
|
+
.with_line("") \
|
|
10
|
+
.with_line("Usage:",style=ui_thread.PrinterText.HEADER) \
|
|
11
|
+
.with_line("\tpynt pynt-id")
|
|
12
|
+
|
|
13
|
+
class PyntShowIdCommand(sub_command.PyntSubCommand):
|
|
14
|
+
def __init__(self, name) -> None:
|
|
15
|
+
super().__init__(name)
|
|
16
|
+
|
|
17
|
+
def usage(self, *args):
|
|
18
|
+
ui_thread.print(pyntid_usage())
|
|
19
|
+
|
|
20
|
+
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
21
|
+
cmd = parent.add_parser(self.name)
|
|
22
|
+
cmd.print_usage = self.usage
|
|
23
|
+
cmd.print_help = self.usage
|
|
24
|
+
return cmd
|
|
25
|
+
|
|
26
|
+
def run_cmd(self, args: argparse.Namespace):
|
|
27
|
+
creds_path = CredStore().get_path()
|
|
28
|
+
ui_thread.print(open(creds_path, "r").read())
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import time
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from pyntcli.store.store import CredStore
|
|
6
|
+
from pyntcli.pynt_docker import pynt_container
|
|
7
|
+
from pyntcli.commands import sub_command, util
|
|
8
|
+
from pyntcli.ui import ui_thread
|
|
9
|
+
from pyntcli.ui.progress import connect_progress_ws, wrap_ws_progress
|
|
10
|
+
|
|
11
|
+
def newman_usage():
|
|
12
|
+
return ui_thread.PrinterText("Integration with newman, run scan using postman collection from the CLI") \
|
|
13
|
+
.with_line("") \
|
|
14
|
+
.with_line("Usage:", style=ui_thread.PrinterText.HEADER) \
|
|
15
|
+
.with_line("\tpynt newman [OPTIONS]") \
|
|
16
|
+
.with_line("") \
|
|
17
|
+
.with_line("Options:", style=ui_thread.PrinterText.HEADER) \
|
|
18
|
+
.with_line("\t--collection - Postman collection file name") \
|
|
19
|
+
.with_line("\t--environment - Postman environment file name") \
|
|
20
|
+
.with_line("\t--reporters output results to json")
|
|
21
|
+
|
|
22
|
+
class NewmanSubCommand(sub_command.PyntSubCommand):
|
|
23
|
+
def __init__(self, name) -> None:
|
|
24
|
+
super().__init__(name)
|
|
25
|
+
|
|
26
|
+
def usage(self, *args):
|
|
27
|
+
ui_thread.print(newman_usage())
|
|
28
|
+
|
|
29
|
+
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
30
|
+
newman_cmd = parent.add_parser(self.name)
|
|
31
|
+
newman_cmd.add_argument("--collection", type=str, required=True)
|
|
32
|
+
newman_cmd.add_argument("--environment", type=str, required=False)
|
|
33
|
+
newman_cmd.add_argument("--reporters", type=bool, required=False)
|
|
34
|
+
newman_cmd.print_usage = self.usage
|
|
35
|
+
newman_cmd.print_help = self.usage
|
|
36
|
+
return newman_cmd
|
|
37
|
+
|
|
38
|
+
def run_cmd(self, args: argparse.Namespace):
|
|
39
|
+
port = str(util.find_open_port())
|
|
40
|
+
docker_type , docker_arguments = pynt_container.get_container_with_arguments(pynt_container.PyntDockerPort(src=port, dest=port, name="--port"))
|
|
41
|
+
mounts = []
|
|
42
|
+
|
|
43
|
+
if not os.path.isfile(args.collection):
|
|
44
|
+
ui_thread.print(ui_thread.PrinterText("Could not find the provided collection path, please provide with a valid collection path", ui_thread.PrinterText.WARNING))
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
collection_name = os.path.basename(args.collection)
|
|
48
|
+
docker_arguments += ["-c", collection_name]
|
|
49
|
+
mounts.append(pynt_container.create_mount(os.path.abspath(args.collection), "/etc/pynt/{}".format(collection_name)))
|
|
50
|
+
|
|
51
|
+
if "environment" in args and args.environment:
|
|
52
|
+
if not os.path.isfile(args.environment):
|
|
53
|
+
ui_thread.print(ui_thread.PrinterText("Could not find the provided environment path, please provide with a valid environment path", ui_thread.PrinterText.WARNING))
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
env_name = os.path.basename(args.environment)
|
|
57
|
+
docker_arguments += ["-e", env_name]
|
|
58
|
+
mounts.append(pynt_container.create_mount(os.path.abspath(args.environment), "/etc/pynt/{}".format(env_name)))
|
|
59
|
+
|
|
60
|
+
mounts.append(pynt_container.create_mount(CredStore().get_path(), "/app/creds.json"))
|
|
61
|
+
|
|
62
|
+
if "reporters" in args and args.reporters:
|
|
63
|
+
open(os.path.join(os.getcwd(), "results.json"), "w").close()
|
|
64
|
+
mounts.append(pynt_container.create_mount(os.path.join(os.getcwd(), "results.json"), "/etc/pynt/results/results.json"))
|
|
65
|
+
docker_arguments.append("--reporters")
|
|
66
|
+
|
|
67
|
+
if "insecure" in args and args.insecure:
|
|
68
|
+
docker_arguments.append("--insecure")
|
|
69
|
+
|
|
70
|
+
if "dev_flags" in args:
|
|
71
|
+
docker_arguments += args.dev_flags.split(" ")
|
|
72
|
+
|
|
73
|
+
newman_docker = pynt_container.PyntContainer(image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
74
|
+
tag="latest",
|
|
75
|
+
detach=True,
|
|
76
|
+
mounts=mounts,
|
|
77
|
+
args=docker_arguments)
|
|
78
|
+
|
|
79
|
+
newman_docker.run(docker_type)
|
|
80
|
+
util.wait_for_healthcheck("http://localhost:{}".format(port))
|
|
81
|
+
ui_thread.print_generator(ui_thread.AnsiText.wrap_gen(newman_docker.stdout))
|
|
82
|
+
|
|
83
|
+
with ui_thread.progress(wrap_ws_progress(connect_progress_ws("ws://localhost:{}/progress".format(port))), "scan in progress..."):
|
|
84
|
+
while newman_docker.is_alive():
|
|
85
|
+
time.sleep(1)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from pyntcli.store.store import CredStore
|
|
5
|
+
from . import sub_command, util
|
|
6
|
+
from pyntcli.pynt_docker import pynt_container
|
|
7
|
+
from pyntcli.ui import ui_thread
|
|
8
|
+
|
|
9
|
+
def postman_usage():
|
|
10
|
+
return ui_thread.PrinterText("Integration with postman, run scan from pynt postman collection") \
|
|
11
|
+
.with_line("") \
|
|
12
|
+
.with_line("Usage:",style=ui_thread.PrinterText.HEADER) \
|
|
13
|
+
.with_line("\tpynt postman [OPTIONS]") \
|
|
14
|
+
.with_line("") \
|
|
15
|
+
.with_line("Options:",style=ui_thread.PrinterText.HEADER) \
|
|
16
|
+
.with_line("\t--port - set the port pynt will listen to (DEFAULT: 5001)") \
|
|
17
|
+
.with_line("\t--insecure - use when target uses self signed certificates")
|
|
18
|
+
|
|
19
|
+
class PostmanSubCommand(sub_command.PyntSubCommand):
|
|
20
|
+
def __init__(self, name) -> None:
|
|
21
|
+
super().__init__(name)
|
|
22
|
+
|
|
23
|
+
def usage(self, *args):
|
|
24
|
+
ui_thread.print(postman_usage())
|
|
25
|
+
|
|
26
|
+
def add_cmd(self, parent_command: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
27
|
+
postman_cmd = parent_command.add_parser(self.name)
|
|
28
|
+
postman_cmd.add_argument("--port", "-p", help="set the port pynt will listen to (DEFAULT: 5001)", type=int, default=5001)
|
|
29
|
+
postman_cmd.print_usage = self.usage
|
|
30
|
+
postman_cmd.print_help = self.usage
|
|
31
|
+
return postman_cmd
|
|
32
|
+
|
|
33
|
+
def run_cmd(self, args: argparse.Namespace):
|
|
34
|
+
docker_type, docker_arguments = pynt_container.get_container_with_arguments(pynt_container.PyntDockerPort("5001", args.port, "--port"))
|
|
35
|
+
|
|
36
|
+
creds_path = CredStore().get_path()
|
|
37
|
+
|
|
38
|
+
if "insecure" in args and args.insecure:
|
|
39
|
+
docker_arguments.append("--insecure")
|
|
40
|
+
|
|
41
|
+
if "dev_flags" in args:
|
|
42
|
+
docker_arguments += args.dev_flags.split(" ")
|
|
43
|
+
|
|
44
|
+
if util.is_port_in_use(args.port):
|
|
45
|
+
ui_thread.print(ui_thread.PrinterText("Port: {} already in use, please use a different one".format(args.port), ui_thread.PrinterText.WARNING))
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
postman_docker = pynt_container.PyntContainer(image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
49
|
+
tag="postman-latest",
|
|
50
|
+
mounts=[pynt_container.create_mount(creds_path, "/app/creds.json")],
|
|
51
|
+
detach=True,
|
|
52
|
+
args=docker_arguments)
|
|
53
|
+
|
|
54
|
+
postman_docker.run(docker_type)
|
|
55
|
+
ui_thread.print_generator(postman_docker.stdout)
|
|
56
|
+
|
|
57
|
+
while postman_docker.is_alive():
|
|
58
|
+
time.sleep(1)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
import os
|
|
4
|
+
import webbrowser
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
import requests
|
|
7
|
+
import time
|
|
8
|
+
from subprocess import Popen, PIPE
|
|
9
|
+
|
|
10
|
+
from pyntcli.store.store import CredStore
|
|
11
|
+
from pyntcli.pynt_docker import pynt_container
|
|
12
|
+
from pyntcli.ui import ui_thread
|
|
13
|
+
from pyntcli.ui.progress import connect_progress_ws, wrap_ws_progress
|
|
14
|
+
from pyntcli.commands import util, sub_command
|
|
15
|
+
|
|
16
|
+
def proxy_usage():
|
|
17
|
+
return ui_thread.PrinterText("Command integration to Pynt. Run a security scan with a given command.") \
|
|
18
|
+
.with_line("") \
|
|
19
|
+
.with_line("Usage:",style=ui_thread.PrinterText.HEADER) \
|
|
20
|
+
.with_line("\tpynt command [OPTIONS]") \
|
|
21
|
+
.with_line("") \
|
|
22
|
+
.with_line("Options:",style=ui_thread.PrinterText.HEADER) \
|
|
23
|
+
.with_line("\t--cmd - The command that runs the functional tests") \
|
|
24
|
+
.with_line("\t--port - Set the port pynt will listen to (DEFAULT: 5001)") \
|
|
25
|
+
.with_line("\t--allow-errors - If present will allow command to fail and continue execution") \
|
|
26
|
+
.with_line("\t--ca-path - The path to the CA file in PEM format") \
|
|
27
|
+
.with_line("\t--proxy-port - Set the port proxied traffic should be routed to (DEFAULT: 6666)") \
|
|
28
|
+
.with_line("\t--report - If present will save the generated report in this path.") \
|
|
29
|
+
.with_line("\t--insecure - use when target uses self signed certificates")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ProxyCommand(sub_command.PyntSubCommand):
|
|
33
|
+
def __init__(self, name) -> None:
|
|
34
|
+
super().__init__(name)
|
|
35
|
+
self.scan_id = ""
|
|
36
|
+
self.proxy_sleep_interval = 2
|
|
37
|
+
self.proxy_healthcheck_buffer = 10
|
|
38
|
+
self.proxy_server_base_url = "http://localhost:{}/api"
|
|
39
|
+
|
|
40
|
+
def print_usage(self, *args):
|
|
41
|
+
ui_thread.print(proxy_usage())
|
|
42
|
+
|
|
43
|
+
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
44
|
+
proxy_cmd = parent.add_parser(self.name)
|
|
45
|
+
proxy_cmd.add_argument("--port", "-p", help="", type=int, default=5001)
|
|
46
|
+
proxy_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
47
|
+
proxy_cmd.add_argument("--cmd", help="", default="", required=True)
|
|
48
|
+
proxy_cmd.add_argument("--allow-errors", action="store_true")
|
|
49
|
+
proxy_cmd.add_argument("--ca-path", type=str, default="")
|
|
50
|
+
proxy_cmd.add_argument("--report", type=str, default="")
|
|
51
|
+
proxy_cmd.print_usage = self.print_usage
|
|
52
|
+
proxy_cmd.print_help = self.print_usage
|
|
53
|
+
return proxy_cmd
|
|
54
|
+
|
|
55
|
+
def _updated_environment(self, args):
|
|
56
|
+
env_copy = deepcopy(os.environ)
|
|
57
|
+
return env_copy.update({"HTTP_PROXY": "http://localhost:{}".format(args.proxy_port),
|
|
58
|
+
"HTTPS_PROXY": "http://localhost:{}".format(args.proxy_port)})
|
|
59
|
+
|
|
60
|
+
def _start_proxy(self, args):
|
|
61
|
+
res = requests.put(self.proxy_server_base_url.format(args.port) + "/proxy/start")
|
|
62
|
+
res.raise_for_status()
|
|
63
|
+
self.scan_id = res.json()["scanId"]
|
|
64
|
+
|
|
65
|
+
def _stop_proxy(self, args):
|
|
66
|
+
start = time.time()
|
|
67
|
+
while start + self.proxy_healthcheck_buffer > time.time():
|
|
68
|
+
res = requests.put(self.proxy_server_base_url.format(args.port) + "/proxy/stop", json={"scanId": self.scan_id})
|
|
69
|
+
if res.status_code == HTTPStatus.OK:
|
|
70
|
+
return
|
|
71
|
+
time.sleep(self.proxy_sleep_interval)
|
|
72
|
+
raise TimeoutError()
|
|
73
|
+
|
|
74
|
+
def _get_report(self, args):
|
|
75
|
+
while True:
|
|
76
|
+
res = requests.get(self.proxy_server_base_url.format(args.port) + "/report", params={"scanId": self.scan_id})
|
|
77
|
+
if res.status_code == HTTPStatus.OK:
|
|
78
|
+
return res.text
|
|
79
|
+
if res.status_code == HTTPStatus.ACCEPTED:
|
|
80
|
+
time.sleep(self.proxy_sleep_interval)
|
|
81
|
+
continue
|
|
82
|
+
if res.status_code == 517: #pynt did not recieve any requests
|
|
83
|
+
ui_thread.print(ui_thread.PrinterText(res.json()["message"], ui_thread.PrinterText.WARNING))
|
|
84
|
+
return
|
|
85
|
+
ui_thread.print("Error in polling for scan report: {}".format(res.text))
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
def run_cmd(self, args: argparse.Namespace):
|
|
89
|
+
docker_type, docker_arguments = pynt_container.get_container_with_arguments(pynt_container.PyntDockerPort(args.port, args.port, "--port"),
|
|
90
|
+
pynt_container.PyntDockerPort(args.proxy_port, args.proxy_port, "--proxy-port"))
|
|
91
|
+
|
|
92
|
+
if "insecure" in args and args.insecure:
|
|
93
|
+
docker_arguments.append("--insecure")
|
|
94
|
+
|
|
95
|
+
if "dev_flags" in args:
|
|
96
|
+
docker_arguments += args.dev_flags.split(" ")
|
|
97
|
+
|
|
98
|
+
mounts = []
|
|
99
|
+
if "ca_path" in args and args.ca_path:
|
|
100
|
+
if not os.path.isfile(args.ca_path):
|
|
101
|
+
ui_thread.print(ui_thread.PrinterText("Could not find the provided ca path, please provide with a valid path", ui_thread.PrinterText.WARNING))
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
ca_name = os.path.basename(args.ca_path)
|
|
105
|
+
docker_arguments += ["--ca-path", ca_name]
|
|
106
|
+
mounts.append(pynt_container.create_mount(os.path.abspath(args.ca_path), "/etc/pynt/{}".format(ca_name)))
|
|
107
|
+
|
|
108
|
+
creds_path = CredStore().get_path()
|
|
109
|
+
mounts.append(pynt_container.create_mount(creds_path, "/app/creds.json"))
|
|
110
|
+
|
|
111
|
+
proxy_docker = pynt_container.PyntContainer(image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
112
|
+
tag="proxy-latest",
|
|
113
|
+
mounts=mounts,
|
|
114
|
+
detach=True,
|
|
115
|
+
args=docker_arguments)
|
|
116
|
+
proxy_docker.run(docker_type)
|
|
117
|
+
ui_thread.print_generator(proxy_docker.stdout)
|
|
118
|
+
|
|
119
|
+
util.wait_for_healthcheck("http://localhost:{}".format(args.port))
|
|
120
|
+
self._start_proxy(args)
|
|
121
|
+
|
|
122
|
+
user_process = Popen(args.cmd, shell=True, stdout=PIPE, stderr=PIPE, env=self._updated_environment(args))
|
|
123
|
+
ui_thread.print_generator(user_process.stdout)
|
|
124
|
+
ui_thread.print_generator(user_process.stderr)
|
|
125
|
+
rc = user_process.wait()
|
|
126
|
+
if rc != 0 and not args.allow_errors:
|
|
127
|
+
proxy_docker.stop()
|
|
128
|
+
ui_thread.print(ui_thread.PrinterText("Command finished with error return code {}, If you wish Pynt to run anyway, run with --allow-errors".format(rc)))
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
self._stop_proxy(args)
|
|
132
|
+
|
|
133
|
+
report = ""
|
|
134
|
+
with ui_thread.progress(wrap_ws_progress(connect_progress_ws("ws://localhost:{}/progress?scanId={}".format(args.port, self.scan_id))), "scan in progress..."):
|
|
135
|
+
report = self._get_report(args)
|
|
136
|
+
if not report:
|
|
137
|
+
proxy_docker.stop()
|
|
138
|
+
return
|
|
139
|
+
report_path = os.path.join(os.getcwd(), "report_{}.html".format(int(time.time())))
|
|
140
|
+
if "report" in args and args.report:
|
|
141
|
+
report_path = os.path.abspath(args.report)
|
|
142
|
+
with open(report_path, "w") as f:
|
|
143
|
+
f.write(report)
|
|
144
|
+
|
|
145
|
+
webbrowser.open("file://{}".format(report_path))
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
from . import postman, root, sub_command, id_command, proxy , newman, har
|
|
5
|
+
|
|
6
|
+
avail_sub_commands = [
|
|
7
|
+
postman.PostmanSubCommand("postman"),
|
|
8
|
+
id_command.PyntShowIdCommand("pynt-id"),
|
|
9
|
+
newman.NewmanSubCommand("newman"),
|
|
10
|
+
har.HarSubCommand("har"),
|
|
11
|
+
proxy.ProxyCommand("command"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
class PyntCommandException(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class BadArgumentsException(PyntCommandException):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class NoSuchCommandException(PyntCommandException):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PyntCommand:
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
self.base: root.BaseCommand = root.BaseCommand()
|
|
27
|
+
self.sub_commands: Dict[str, sub_command.PyntSubCommand] = {sc.get_name(): sc for sc in avail_sub_commands}
|
|
28
|
+
self._start_command()
|
|
29
|
+
|
|
30
|
+
def _start_command(self):
|
|
31
|
+
self.base.cmd()
|
|
32
|
+
for sc in self.sub_commands.values():
|
|
33
|
+
self.base.add_base_arguments(sc.add_cmd(self.base.get_subparser()))
|
|
34
|
+
|
|
35
|
+
def parse_args(self, args_from_cmd: List[str]):
|
|
36
|
+
return self.base.cmd().parse_args(args_from_cmd)
|
|
37
|
+
|
|
38
|
+
def run_cmd(self, args: argparse.Namespace):
|
|
39
|
+
if not "command" in args:
|
|
40
|
+
raise BadArgumentsException()
|
|
41
|
+
|
|
42
|
+
command = getattr(args, "command")
|
|
43
|
+
if not command in self.sub_commands:
|
|
44
|
+
raise NoSuchCommandException()
|
|
45
|
+
|
|
46
|
+
self.base.run_cmd(args)
|
|
47
|
+
self.sub_commands[command].run_cmd(args)
|