stoobly-agent 1.0.5__py3-none-any.whl → 1.0.6__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.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/cli/ca_cert_cli.py +29 -18
- stoobly_agent/app/cli/helpers/certificate_authority.py +201 -0
- stoobly_agent/app/cli/scaffold/app.py +29 -1
- stoobly_agent/app/cli/scaffold/constants.py +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +13 -19
- stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +8 -5
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.mkcert +3 -0
- stoobly_agent/app/cli/scaffold/workflow.py +1 -11
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +12 -6
- stoobly_agent/app/cli/scaffold_cli.py +51 -9
- stoobly_agent/app/proxy/replay/replay_request_service.py +3 -2
- stoobly_agent/app/settings/proxy_settings.py +0 -1
- stoobly_agent/cli.py +2 -2
- stoobly_agent/config/constants/mitmproxy.py +4 -0
- stoobly_agent/config/data_dir.py +33 -0
- stoobly_agent/config/mitmproxy.py +3 -9
- stoobly_agent/test/app/cli/helpers/certificate_authority_test.py +77 -0
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/config/data_dir_test.py +8 -1
- {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.6.dist-info}/METADATA +2 -2
- {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.6.dist-info}/RECORD +26 -23
- {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.6.dist-info}/WHEEL +1 -1
- stoobly_agent/app/cli/ca_cert_installer.py +0 -54
- {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.6.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.6.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '1.0.
|
2
|
+
VERSION = '1.0.6'
|
@@ -1,8 +1,10 @@
|
|
1
|
-
|
2
1
|
import click
|
3
|
-
import
|
2
|
+
import pdb
|
3
|
+
import sys
|
4
|
+
|
5
|
+
from stoobly_agent.config.data_dir import DataDir
|
4
6
|
|
5
|
-
from .
|
7
|
+
from .helpers.certificate_authority import CertificateAuthority
|
6
8
|
|
7
9
|
@click.group(
|
8
10
|
epilog="Run 'stoobly-agent COMMAND --help' for more information on a command.",
|
@@ -13,22 +15,31 @@ def ca_cert(ctx):
|
|
13
15
|
pass
|
14
16
|
|
15
17
|
@ca_cert.command()
|
18
|
+
@click.option('--ca-certs-dir-path', default=DataDir.instance().mitmproxy_conf_dir_path, help='Path to ca certs directory used to sign SSL certs. Defaults to ~/.mitmproxy')
|
19
|
+
@click.option('--certs-dir-path', default=DataDir.instance().certs_dir_path, help='Output directory.')
|
20
|
+
@click.argument('hostname')
|
21
|
+
def mkcert(**kwargs):
|
22
|
+
mitmproxy_ca_certs_dir = kwargs['ca_certs_dir_path']
|
23
|
+
|
24
|
+
installer = CertificateAuthority(mitmproxy_ca_certs_dir)
|
25
|
+
|
26
|
+
try:
|
27
|
+
installer.sign(kwargs['hostname'], kwargs['certs_dir_path'])
|
28
|
+
except Exception as e:
|
29
|
+
print(e, file=sys.stderr)
|
30
|
+
sys.exit(1)
|
31
|
+
|
32
|
+
@ca_cert.command()
|
33
|
+
@click.option('--ca-certs-dir-path', default=DataDir.instance().mitmproxy_conf_dir_path, help='Path to ca certs directory.')
|
16
34
|
def install(**kwargs):
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
print(
|
24
|
-
|
25
|
-
# MacOS
|
26
|
-
elif distro.id() == 'darwin':
|
27
|
-
installer.handle_darwin()
|
28
|
-
# elif distro.id() == 'rhel':
|
29
|
-
# return
|
30
|
-
else:
|
31
|
-
print(f"{distro_name} is not supported yet for automatic CA cert installation.")
|
35
|
+
mitmproxy_ca_certs_dir = kwargs['ca_certs_dir_path']
|
36
|
+
installer = CertificateAuthority(mitmproxy_ca_certs_dir)
|
37
|
+
|
38
|
+
try:
|
39
|
+
installer.install()
|
40
|
+
except Exception as e:
|
41
|
+
print(e, file=sys.stderr)
|
42
|
+
sys.exit(1)
|
32
43
|
|
33
44
|
@ca_cert.command()
|
34
45
|
def uninstall():
|
@@ -0,0 +1,201 @@
|
|
1
|
+
import distro
|
2
|
+
import os
|
3
|
+
import pdb
|
4
|
+
import socket
|
5
|
+
import ssl
|
6
|
+
import subprocess
|
7
|
+
|
8
|
+
from cryptography import x509
|
9
|
+
from cryptography.hazmat.backends import default_backend
|
10
|
+
from cryptography.hazmat.primitives import serialization
|
11
|
+
from cryptography.x509.oid import NameOID
|
12
|
+
from mitmproxy.certs import CertStore
|
13
|
+
from pathlib import Path
|
14
|
+
|
15
|
+
from stoobly_agent.config.data_dir import DataDir
|
16
|
+
from stoobly_agent.lib.logger import Logger
|
17
|
+
|
18
|
+
LOG_ID = 'CertificateAuthority'
|
19
|
+
|
20
|
+
MITMPROXY_CN = 'mitmproxy'
|
21
|
+
|
22
|
+
class CertificateAuthority():
|
23
|
+
|
24
|
+
def __init__(self, certs_dir = DataDir.instance().mitmproxy_conf_dir_path, cn = MITMPROXY_CN):
|
25
|
+
self.certs_dir = certs_dir
|
26
|
+
self.cn = cn
|
27
|
+
self.key_size = 2048
|
28
|
+
|
29
|
+
@property
|
30
|
+
def certs_generated(self):
|
31
|
+
if not os.path.exists(self.ca_cert_path('.pem')):
|
32
|
+
return False
|
33
|
+
|
34
|
+
if not os.path.exists(self.ca_cert_path('.cer')):
|
35
|
+
return False
|
36
|
+
|
37
|
+
if not os.path.exists(self.ca_path('.pem')):
|
38
|
+
return False
|
39
|
+
|
40
|
+
return True
|
41
|
+
|
42
|
+
def ca_file_name(self, extension: str):
|
43
|
+
return '{cn}-ca{extension}'.format(cn=self.cn, extension=extension)
|
44
|
+
|
45
|
+
def ca_path(self, extension: str):
|
46
|
+
file_name = self.ca_file_name(extension)
|
47
|
+
return os.path.join(self.certs_dir, file_name)
|
48
|
+
|
49
|
+
def ca_cert_file_name(self, extension: str):
|
50
|
+
return '{cn}-ca-cert{extension}'.format(cn=self.cn, extension=extension)
|
51
|
+
|
52
|
+
def ca_cert_path(self, extension: str):
|
53
|
+
file_name = self.ca_cert_file_name(extension)
|
54
|
+
return os.path.join(self.certs_dir, file_name)
|
55
|
+
|
56
|
+
def generate_certs(self):
|
57
|
+
if not os.path.exists(self.certs_dir):
|
58
|
+
os.makedirs(self.certs_dir)
|
59
|
+
|
60
|
+
path = Path(self.certs_dir)
|
61
|
+
CertStore.create_store(path, self.cn, self.key_size)
|
62
|
+
|
63
|
+
# https://askubuntu.com/a/94861
|
64
|
+
def handle_debian(self):
|
65
|
+
extra_ca_certs_dir = '/usr/local/share/ca-certificates/extra'
|
66
|
+
|
67
|
+
ca_cert_path = self.ca_cert_path('.cer')
|
68
|
+
self.__ensure_exists(ca_cert_path)
|
69
|
+
|
70
|
+
extra_crt_absolute_path = os.path.join(extra_ca_certs_dir, self.ca_cert_file_name('.crt'))
|
71
|
+
|
72
|
+
subprocess.run(f"sudo mkdir -p {extra_ca_certs_dir}".split(), check=True)
|
73
|
+
subprocess.run(f"sudo cp {ca_cert_path} {extra_crt_absolute_path}".split(), check=True)
|
74
|
+
subprocess.run("sudo update-ca-certificates".split(), check=True)
|
75
|
+
|
76
|
+
# https://www.dssw.co.uk/reference/security.html
|
77
|
+
def handle_darwin(self):
|
78
|
+
system_keychain_path = '/Library/Keychains/System.keychain'
|
79
|
+
|
80
|
+
ca_cert_path = self.ca_cert_path('.pem')
|
81
|
+
self.__ensure_exists(ca_cert_path)
|
82
|
+
|
83
|
+
subprocess.run(f"sudo security add-trusted-cert -d -p ssl -p basic -k {system_keychain_path} {ca_cert_path}".split(), check=True)
|
84
|
+
|
85
|
+
def handle_rhel(self):
|
86
|
+
ca_trust_dir = '/etc/pki/ca-trust/source/anchors'
|
87
|
+
|
88
|
+
ca_cert_path = self.ca_cert_path('.cer')
|
89
|
+
self.__ensure_exists(ca_cert_path)
|
90
|
+
|
91
|
+
ca_trust_crt_absolute_path = os.path.join(ca_trust_dir, self.crt_file_name)
|
92
|
+
|
93
|
+
subprocess.run(f"sudo cp {ca_cert_path} {ca_trust_crt_absolute_path}".split(), check=True)
|
94
|
+
subprocess.run("sudo update-ca-trust extract".split(), check=True)
|
95
|
+
|
96
|
+
def install(self):
|
97
|
+
distro_name = distro.name(pretty=True)
|
98
|
+
|
99
|
+
# Ubuntu or other Debian based
|
100
|
+
if distro.like() == 'debian':
|
101
|
+
self.handle_debian()
|
102
|
+
# MacOS
|
103
|
+
elif distro.id() == 'darwin':
|
104
|
+
self.handle_darwin()
|
105
|
+
# elif distro.id() == 'rhel':
|
106
|
+
# return
|
107
|
+
else:
|
108
|
+
raise TypeError(f"{distro_name} is not supported yet for automatic CA cert installation.")
|
109
|
+
|
110
|
+
def sign(self, hostname: str, dest = None, port = 443):
|
111
|
+
dest = dest or self.certs_dir
|
112
|
+
|
113
|
+
if self.signed(hostname, dest):
|
114
|
+
return False
|
115
|
+
|
116
|
+
store = CertStore.from_store(self.certs_dir, self.cn, self.key_size)
|
117
|
+
organization, alt_names = self.__cert_details(hostname, port)
|
118
|
+
cert_entry = store.get_cert(alt_names[0], alt_names, organization)
|
119
|
+
|
120
|
+
crt_path = os.path.join(dest, f"{hostname}.crt")
|
121
|
+
with open(crt_path, 'wb') as fp:
|
122
|
+
cert = cert_entry.cert
|
123
|
+
crt = cert.to_pem()
|
124
|
+
fp.write(crt)
|
125
|
+
|
126
|
+
key_path = os.path.join(dest, f"{hostname}.key")
|
127
|
+
with open(key_path, 'wb') as fp:
|
128
|
+
private_key = cert_entry.privatekey
|
129
|
+
key = private_key.private_bytes(
|
130
|
+
encoding=serialization.Encoding.PEM,
|
131
|
+
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
132
|
+
encryption_algorithm=serialization.NoEncryption(),
|
133
|
+
)
|
134
|
+
fp.write(key)
|
135
|
+
|
136
|
+
joined_path = os.path.join(dest, f"{hostname}-joined.pem")
|
137
|
+
with open(joined_path, 'wb') as fp:
|
138
|
+
fp.write(b"\n".join([crt, key]))
|
139
|
+
|
140
|
+
return True
|
141
|
+
|
142
|
+
def signed(self, hostname: str, dest = None):
|
143
|
+
dest = dest or self.certs_dir
|
144
|
+
|
145
|
+
crt_path = os.path.join(dest, f"{hostname}.crt")
|
146
|
+
if not os.path.exists(crt_path):
|
147
|
+
return False
|
148
|
+
|
149
|
+
key_path = os.path.join(dest, f"{hostname}.key")
|
150
|
+
if not os.path.exists(key_path):
|
151
|
+
return False
|
152
|
+
|
153
|
+
joined_path = os.path.join(dest, f"{hostname}-joined.pem")
|
154
|
+
if not os.path.exists(joined_path):
|
155
|
+
return False
|
156
|
+
|
157
|
+
return True
|
158
|
+
|
159
|
+
def __ensure_exists(self, file_path):
|
160
|
+
if not os.path.exists(file_path):
|
161
|
+
self.generate_certs()
|
162
|
+
|
163
|
+
def __cert_details(self, domain: str, port: int = 443):
|
164
|
+
"""
|
165
|
+
Retrieves the organization name and subject alternative names from an HTTPS domain's certificate.
|
166
|
+
|
167
|
+
:param domain: The domain name (e.g., 'example.com').
|
168
|
+
:param port: The port number (default: 443 for HTTPS).
|
169
|
+
:return: A tuple containing the organization name and a list of alternative names (or None if not found).
|
170
|
+
"""
|
171
|
+
alt_names = [domain]
|
172
|
+
try:
|
173
|
+
# Connect to the server and get the certificate
|
174
|
+
context = ssl.create_default_context()
|
175
|
+
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
176
|
+
timeout = 2.5
|
177
|
+
with socket.create_connection((domain, port), timeout) as sock:
|
178
|
+
with context.wrap_socket(sock, server_hostname=domain) as ssock:
|
179
|
+
cert_der = ssock.getpeercert(binary_form=True)
|
180
|
+
|
181
|
+
# Parse the certificate
|
182
|
+
cert = x509.load_der_x509_certificate(cert_der, default_backend())
|
183
|
+
|
184
|
+
# Extract the organization name
|
185
|
+
try:
|
186
|
+
org_name = cert.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)[0].value
|
187
|
+
except IndexError:
|
188
|
+
org_name = None
|
189
|
+
|
190
|
+
# Extract the Subject Alternative Names (SANs)
|
191
|
+
try:
|
192
|
+
ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
193
|
+
alt_names.extend(ext.value.get_values_for_type(x509.DNSName))
|
194
|
+
except x509.ExtensionNotFound:
|
195
|
+
alt_names = []
|
196
|
+
|
197
|
+
return org_name, alt_names
|
198
|
+
|
199
|
+
except Exception as e:
|
200
|
+
Logger.instance(LOG_ID).warn(f"Could not retrieve certificate for {domain}: {e}")
|
201
|
+
return None, alt_names
|
@@ -10,7 +10,8 @@ class App():
|
|
10
10
|
data_dir: DataDir = DataDir.instance(path)
|
11
11
|
|
12
12
|
self.__scaffold_dir_path = data_dir.path
|
13
|
-
self.
|
13
|
+
self.__ca_certs_dir_path = kwargs.get('ca_certs_dir_path') or data_dir.mitmproxy_conf_dir_path
|
14
|
+
self.__certs_dir_path = data_dir.certs_dir_path
|
14
15
|
self.__context_dir_path = data_dir.context_dir_path
|
15
16
|
self.__dir_path = path
|
16
17
|
self.__name = os.path.basename(self.__dir_path)
|
@@ -18,6 +19,15 @@ class App():
|
|
18
19
|
self.__namespace = namespace
|
19
20
|
self.__skip_validate_path = not not kwargs.get('skip_validate_path')
|
20
21
|
|
22
|
+
@property
|
23
|
+
def ca_certs_dir_path(self):
|
24
|
+
return self.__ca_certs_dir_path
|
25
|
+
|
26
|
+
@ca_certs_dir_path.setter
|
27
|
+
def ca_certs_dir_path(self, v: str):
|
28
|
+
self.__validate_path(v)
|
29
|
+
self.__certs_dir_path = v
|
30
|
+
|
21
31
|
@property
|
22
32
|
def certs_dir_path(self):
|
23
33
|
return self.__certs_dir_path
|
@@ -80,6 +90,24 @@ class App():
|
|
80
90
|
def scaffold_namespace_path(self):
|
81
91
|
return os.path.join(self.scaffold_dir_path, self.namespace)
|
82
92
|
|
93
|
+
@property
|
94
|
+
def services(self):
|
95
|
+
return list(map(lambda path: os.path.basename(path), self.service_paths))
|
96
|
+
|
97
|
+
@property
|
98
|
+
def service_paths(self):
|
99
|
+
services_dir = os.path.join(self.scaffold_dir_path, self.namespace)
|
100
|
+
|
101
|
+
services = []
|
102
|
+
for filename in os.listdir(services_dir):
|
103
|
+
path = os.path.join(services_dir, filename)
|
104
|
+
if not os.path.isdir(path):
|
105
|
+
continue
|
106
|
+
|
107
|
+
services.append(path)
|
108
|
+
|
109
|
+
return services
|
110
|
+
|
83
111
|
def copy_folders_and_hidden_files(self, src, dst):
|
84
112
|
os.makedirs(dst, exist_ok=True)
|
85
113
|
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# Overridable Environment Variables
|
2
2
|
#
|
3
3
|
# STOOBLY_APP_DIR: path to the application source code directory
|
4
|
-
#
|
4
|
+
# STOOBLY_CA_CERTS_DIR: path to folder where ca certs are stored
|
5
5
|
# STOOBLY_CERTS_DIR: path to a folder to store certs
|
6
|
+
# STOOBLY_CONTEXT_DIR: path to the folder containing the .stoobly folder
|
6
7
|
# STOOBLY_WORKFLOW_RUN_OPTIONS: extra options to pass to 'stoobly-agent scaffold workflow run' command
|
7
8
|
|
8
9
|
# Constants
|
@@ -13,6 +14,7 @@ CONTEXT_DIR_DEFAULT := $(realpath $(DIR)/../..)
|
|
13
14
|
|
14
15
|
# Configuration
|
15
16
|
app_dir=$$(realpath "$${STOOBLY_APP_DIR:-$(CONTEXT_DIR_DEFAULT)}")
|
17
|
+
ca_certs_dir=$$(realpath "$${STOOBLY_CA_CERTS_DIR:-$$(realpath ~/.mitmproxy)}")
|
16
18
|
certs_dir=$$(realpath "$${STOOBLY_CERTS_DIR:-$(app_data_dir)/certs}")
|
17
19
|
context_dir=$$(realpath "$${STOOBLY_CONTEXT_DIR:-$(CONTEXT_DIR_DEFAULT)}")
|
18
20
|
workflow_run_options=$${STOOBLY_WORKFLOW_RUN_OPTIONS:+$$STOOBLY_WORKFLOW_RUN_OPTIONS }
|
@@ -26,29 +28,21 @@ source_env=set -a; [ -f .env ] && source .env; set +a
|
|
26
28
|
|
27
29
|
docker_compose_file_path=$(app_data_dir)/docker/stoobly-ui/exec/.docker-compose.exec.yml
|
28
30
|
stoobly_exec_args=--profile $(WORKFLOW_NAME) -p $(WORKFLOW_NAME) up --build --remove-orphans
|
29
|
-
stoobly_exec_env=export CONTEXT_DIR=$(context_dir) && export USER_ID=$$UID
|
31
|
+
stoobly_exec_env=export CONTEXT_DIR=$(context_dir) && export USER_ID=$$UID && export CA_CERTS_DIR="$(ca_certs_dir)"
|
30
32
|
stoobly_exec=$(stoobly_exec_env) && $(source_env) && $(docker_compose_command) -f "$(docker_compose_file_path)" $(stoobly_exec_args)
|
31
33
|
|
32
34
|
# Because scaffold is stored in the APP_DIR, when running a scaffold command from within a container,
|
33
35
|
# it needs access to APP_DIR rather than CONTEXT_DIR
|
34
|
-
stoobly_exec_run_env=export USER_ID=$$UID
|
36
|
+
stoobly_exec_run_env=export USER_ID=$$UID && export CA_CERTS_DIR="$(ca_certs_dir)"
|
35
37
|
stoobly_exec_run=$(stoobly_exec_run_env) && $(source_env) && CONTEXT_DIR=$(app_dir) $(docker_compose_command) -f "$(docker_compose_file_path)" $(stoobly_exec_args)
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
workflow_run=$(
|
39
|
+
workflow_run_script=$(app_data_dir)/tmp/run.sh
|
40
|
+
workflow_run_env=export APP_DIR="$(app_dir)" && export CERTS_DIR="$(certs_dir)" && export CONTEXT_DIR="$(context_dir)"
|
41
|
+
workflow_run=$(workflow_run_env) && $(source_env) && bash "$(workflow_run_script)"
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
else \
|
45
|
-
mkdir -p "$(certs_dir)" && \
|
46
|
-
cd "$(certs_dir)" && \
|
47
|
-
mkcert --install && \
|
48
|
-
mkcert $(hostname) && \
|
49
|
-
cp $(hostname).pem $(hostname).crt && \
|
50
|
-
cp $(hostname)-key.pem $(hostname).key; \
|
51
|
-
fi
|
43
|
+
certs:
|
44
|
+
export EXEC_COMMAND=bin/.mkcert && \
|
45
|
+
$(stoobly_exec)
|
52
46
|
intercept/disable:
|
53
47
|
export EXEC_COMMAND=bin/.disable && \
|
54
48
|
$(stoobly_exec)
|
@@ -56,7 +50,7 @@ intercept/enable:
|
|
56
50
|
export EXEC_COMMAND=bin/.enable && \
|
57
51
|
export EXEC_ARGS=$(scenario_key) && \
|
58
52
|
$(stoobly_exec)
|
59
|
-
mock:
|
53
|
+
mock: certs
|
60
54
|
export EXEC_COMMAND=bin/.run && \
|
61
55
|
export EXEC_OPTIONS="$(workflow_run_options)$(options)" && \
|
62
56
|
export EXEC_ARGS="mock" && \
|
@@ -68,7 +62,7 @@ mock/stop:
|
|
68
62
|
export EXEC_ARGS="mock" && \
|
69
63
|
$(stoobly_exec_run) && \
|
70
64
|
$(workflow_run)
|
71
|
-
record:
|
65
|
+
record: certs
|
72
66
|
export EXEC_COMMAND=bin/.run && \
|
73
67
|
export EXEC_OPTIONS="$(workflow_run_options)$(options)" && \
|
74
68
|
export EXEC_ARGS="record" && \
|
@@ -1,15 +1,18 @@
|
|
1
1
|
services:
|
2
|
+
ui_base:
|
3
|
+
extends:
|
4
|
+
service: context_base
|
5
|
+
volumes:
|
6
|
+
- ${CA_CERTS_DIR}:/home/stoobly/.mitmproxy
|
2
7
|
context_base:
|
3
8
|
build:
|
4
9
|
args:
|
5
10
|
USER_ID: ${USER_ID}
|
6
11
|
dockerfile: ./.Dockerfile.context
|
7
12
|
volumes:
|
8
|
-
- ${CONTEXT_DIR}/.stoobly:/home/stoobly/.stoobly
|
13
|
+
- ${CONTEXT_DIR}/.stoobly:/home/stoobly/.stoobly
|
9
14
|
proxy_base:
|
10
15
|
build:
|
11
|
-
args:
|
12
|
-
USER_ID: ${USER_ID}
|
13
16
|
dockerfile: ./.Dockerfile.proxy
|
14
|
-
|
15
|
-
|
17
|
+
extends:
|
18
|
+
service: context_base
|
@@ -32,17 +32,7 @@ class Workflow():
|
|
32
32
|
|
33
33
|
@property
|
34
34
|
def service_paths(self):
|
35
|
-
|
36
|
-
|
37
|
-
services = []
|
38
|
-
for filename in os.listdir(services_dir):
|
39
|
-
path = os.path.join(services_dir, filename)
|
40
|
-
if not os.path.isdir(path):
|
41
|
-
continue
|
42
|
-
|
43
|
-
services.append(path)
|
44
|
-
|
45
|
-
return services
|
35
|
+
return self.app.service_paths
|
46
36
|
|
47
37
|
def service_paths_from_services(self, services: List[str]):
|
48
38
|
app_namespace_path = self.app.namespace_path
|
@@ -5,7 +5,7 @@ from stoobly_agent.config.data_dir import DataDir
|
|
5
5
|
from stoobly_agent.lib.logger import Logger
|
6
6
|
|
7
7
|
from .app import App
|
8
|
-
from .constants import APP_NETWORK_ENV, CERTS_DIR_ENV, CONTEXT_DIR_ENV, SERVICE_NAME_ENV, USER_ID_ENV, WORKFLOW_NAME_ENV
|
8
|
+
from .constants import APP_NETWORK_ENV, CA_CERTS_DIR_ENV, CERTS_DIR_ENV, CONTEXT_DIR_ENV, SERVICE_NAME_ENV, USER_ID_ENV, WORKFLOW_NAME_ENV
|
9
9
|
from .env import Env
|
10
10
|
from .workflow_command import WorkflowCommand
|
11
11
|
|
@@ -16,18 +16,23 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
16
16
|
super().__init__(app, **kwargs)
|
17
17
|
|
18
18
|
self.__current_working_dir = os.getcwd()
|
19
|
+
self.__ca_certs_dir_path = app.ca_certs_dir_path
|
19
20
|
self.__certs_dir_path = app.certs_dir_path
|
20
21
|
self.__context_dir_path = app.context_dir_path
|
21
22
|
self.__extra_compose_path = kwargs.get('extra_compose_path')
|
22
23
|
self.__network = kwargs.get('network') or app.network
|
23
24
|
|
25
|
+
@property
|
26
|
+
def ca_certs_dir_path(self):
|
27
|
+
if not os.path.exists(self.__ca_certs_dir_path):
|
28
|
+
os.makedirs(self.__ca_certs_dir_path)
|
29
|
+
|
30
|
+
return self.__ca_certs_dir_path
|
31
|
+
|
24
32
|
@property
|
25
33
|
def certs_dir_path(self):
|
26
|
-
if not self.__certs_dir_path:
|
27
|
-
|
28
|
-
dir_path = os.path.join(data_dir.tmp_dir_path, 'certs')
|
29
|
-
if not os.path.exists(dir_path):
|
30
|
-
os.mkdir(dir_path)
|
34
|
+
if not os.path.exists(self.__certs_dir_path):
|
35
|
+
os.makedirs(self.__certs_dir_path)
|
31
36
|
|
32
37
|
return self.__certs_dir_path
|
33
38
|
|
@@ -120,6 +125,7 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
120
125
|
|
121
126
|
def write_env(self):
|
122
127
|
_config = {}
|
128
|
+
_config[CA_CERTS_DIR_ENV] = self.ca_certs_dir_path
|
123
129
|
_config[CERTS_DIR_ENV] = self.certs_dir_path
|
124
130
|
_config[CONTEXT_DIR_ENV] = self.context_dir_path
|
125
131
|
_config[SERVICE_NAME_ENV] = self.service_name
|
@@ -5,6 +5,7 @@ import sys
|
|
5
5
|
|
6
6
|
from typing import List
|
7
7
|
|
8
|
+
from stoobly_agent.app.cli.helpers.certificate_authority import CertificateAuthority
|
8
9
|
from stoobly_agent.app.cli.helpers.shell import exec_stream
|
9
10
|
from stoobly_agent.app.cli.scaffold.app import App
|
10
11
|
from stoobly_agent.app.cli.scaffold.app_create_command import AppCreateCommand
|
@@ -68,10 +69,45 @@ def create(**kwargs):
|
|
68
69
|
else:
|
69
70
|
print(f"{kwargs['app_dir_path']} already exists, use option '--force' to continue ")
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
@app.command(
|
73
|
+
help="Scaffold app service certs"
|
74
|
+
)
|
75
|
+
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
76
|
+
@click.option('--ca-certs-dir-path', default=DataDir.instance().mitmproxy_conf_dir_path, help='Path to ca certs directory used to sign SSL certs. Defaults to ~/.mitmproxy')
|
77
|
+
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
|
78
|
+
@click.option('--context-dir-path', default=DataDir.instance().context_dir_path, help='Path to Stoobly data directory.')
|
79
|
+
@click.option('--service', multiple=True, help='Select which services to run. Defaults to all.')
|
80
|
+
def mkcert(**kwargs):
|
81
|
+
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, ca_certs_dir_path=kwargs['ca_certs_dir_path'])
|
82
|
+
|
83
|
+
if kwargs['certs_dir_path']:
|
84
|
+
app.certs_dir_path = kwargs['certs_dir_path']
|
85
|
+
|
86
|
+
if kwargs['context_dir_path']:
|
87
|
+
app.context_dir_path = kwargs['context_dir_path']
|
88
|
+
|
89
|
+
if not app.exists:
|
90
|
+
print(f"Error: {app.dir_path} does not exist", file=sys.stderr)
|
91
|
+
sys.exit(1)
|
92
|
+
|
93
|
+
services = __get_services(app.services, service=kwargs['service'])
|
94
|
+
|
95
|
+
for service_name in services:
|
96
|
+
service = Service(service_name, app)
|
97
|
+
service_config = ServiceConfig(service.dir_path)
|
98
|
+
|
99
|
+
if service_config.scheme != 'https':
|
100
|
+
continue
|
101
|
+
|
102
|
+
hostname = service_config.hostname
|
103
|
+
|
104
|
+
if not hostname:
|
105
|
+
continue
|
106
|
+
|
107
|
+
ca = CertificateAuthority(app.ca_certs_dir_path)
|
108
|
+
if not ca.signed(hostname, app.certs_dir_path):
|
109
|
+
Logger.instance(LOG_ID).info(f"Creating cert for {hostname}")
|
110
|
+
ca.sign(hostname, app.certs_dir_path)
|
75
111
|
|
76
112
|
@service.command(
|
77
113
|
help="Scaffold a service",
|
@@ -273,7 +309,8 @@ def logs(**kwargs):
|
|
273
309
|
|
274
310
|
@workflow.command()
|
275
311
|
@click.option('--app-dir-path', default=os.getcwd(), help='Path to application directory.')
|
276
|
-
@click.option('--certs-dir-path', default=DataDir.instance().
|
312
|
+
@click.option('--ca-certs-dir-path', default=DataDir.instance().mitmproxy_conf_dir_path, help='Path to ca certs directory used to sign SSL certs. Defaults to ~/.mitmproxy')
|
313
|
+
@click.option('--certs-dir-path', help='Path to certs directory. Defaults to the certs dir of the context.')
|
277
314
|
@click.option('--context-dir-path', default=DataDir.instance().context_dir_path, help='Path to Stoobly data directory.')
|
278
315
|
@click.option('--filter', multiple=True, type=click.Choice([WORKFLOW_CUSTOM_FILTER]), help='Select which service groups to run. Defaults to all.')
|
279
316
|
@click.option('--dry-run', default=False, is_flag=True, help='If set, prints commands.')
|
@@ -290,7 +327,11 @@ def run(**kwargs):
|
|
290
327
|
if not os.getenv(env_vars.LOG_LEVEL):
|
291
328
|
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
292
329
|
|
293
|
-
app = App(
|
330
|
+
app = App(
|
331
|
+
kwargs['app_dir_path'], DOCKER_NAMESPACE,
|
332
|
+
ca_certs_dir_path=kwargs['ca_certs_dir_path'], skip_validate_path=kwargs['dry_run']
|
333
|
+
)
|
334
|
+
|
294
335
|
if kwargs['certs_dir_path']:
|
295
336
|
app.certs_dir_path = kwargs['certs_dir_path']
|
296
337
|
|
@@ -344,16 +385,17 @@ def __get_services(services: List[str], **kwargs):
|
|
344
385
|
# Log services that don't exist
|
345
386
|
missing_services = [service for service in kwargs['service'] if service not in services]
|
346
387
|
if missing_services:
|
347
|
-
Logger.instance(
|
388
|
+
Logger.instance(LOG_ID).warn(f"Service(s) {','.join(missing_services)} are not found")
|
348
389
|
|
390
|
+
filter = kwargs.get('filter') or []
|
349
391
|
if kwargs['service']:
|
350
392
|
# If service is specified, run only those services
|
351
393
|
services = list(kwargs['service'])
|
352
394
|
|
353
|
-
if WORKFLOW_CUSTOM_FILTER not in
|
395
|
+
if WORKFLOW_CUSTOM_FILTER not in filter:
|
354
396
|
services += CORE_SERVICES
|
355
397
|
else:
|
356
|
-
if WORKFLOW_CUSTOM_FILTER in
|
398
|
+
if WORKFLOW_CUSTOM_FILTER in filter:
|
357
399
|
# If this filter is set, then the user does not want to run core services
|
358
400
|
services = list(filter(lambda service: service not in CORE_SERVICES, services))
|
359
401
|
|
@@ -5,7 +5,7 @@ import requests
|
|
5
5
|
from time import time
|
6
6
|
from typing import Callable, TypedDict, Union
|
7
7
|
|
8
|
-
from stoobly_agent.app.cli.
|
8
|
+
from stoobly_agent.app.cli.helpers.certificate_authority import CertificateAuthority
|
9
9
|
from stoobly_agent.app.cli.helpers.context import ReplayContext
|
10
10
|
from stoobly_agent.app.models.adapters.python import PythonResponseAdapterFactory
|
11
11
|
from stoobly_agent.app.models.schemas.request import Request
|
@@ -125,6 +125,7 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
|
|
125
125
|
else:
|
126
126
|
settings = Settings.instance()
|
127
127
|
handler = getattr(requests, method.lower())
|
128
|
+
ca = CertificateAuthority()
|
128
129
|
|
129
130
|
res = handler(
|
130
131
|
request.url,
|
@@ -135,7 +136,7 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
|
|
135
136
|
'http': settings.proxy.url,
|
136
137
|
'https': settings.proxy.url,
|
137
138
|
},
|
138
|
-
'verify':
|
139
|
+
'verify': ca.ca_cert_path('.crt') if request_config['verify'] else False,
|
139
140
|
}
|
140
141
|
)
|
141
142
|
received_at = time()
|
stoobly_agent/cli.py
CHANGED
@@ -11,7 +11,7 @@ from stoobly_agent.app.cli.helpers.handle_mock_service import print_raw_response
|
|
11
11
|
from stoobly_agent.app.cli.helpers.validations import validate_project_key, validate_scenario_key
|
12
12
|
from stoobly_agent.app.proxy.constants import custom_response_codes
|
13
13
|
from stoobly_agent.app.proxy.replay.replay_request_service import replay as replay_request
|
14
|
-
from stoobly_agent.config.constants import env_vars, mode
|
14
|
+
from stoobly_agent.config.constants import env_vars, mitmproxy, mode
|
15
15
|
from stoobly_agent.config.data_dir import DataDir
|
16
16
|
from stoobly_agent.lib.utils.conditional_decorator import ConditionalDecorator
|
17
17
|
|
@@ -87,7 +87,7 @@ def init(**kwargs):
|
|
87
87
|
Passphrase for decrypting the private key provided in the --cert option. Note that passing cert_passphrase on the command line makes your passphrase visible in your system's process list. Specify it in
|
88
88
|
config.yaml to avoid this.
|
89
89
|
''')
|
90
|
-
@click.option('--confdir', default=
|
90
|
+
@click.option('--confdir', default=mitmproxy.DEFAULT_CONF_DIR_PATH, help='Location of the default mitmproxy configuration files.')
|
91
91
|
@click.option('--connection-strategy', help=', '.join(CONNECTION_STRATEGIES), type=click.Choice(CONNECTION_STRATEGIES))
|
92
92
|
@click.option('--flow-detail', default='1', type=click.Choice(['0', '1', '2', '3', '4']), help='''
|
93
93
|
The display detail level for flows in mitmdump: 0 (quiet) to 4 (very verbose).
|
stoobly_agent/config/data_dir.py
CHANGED
@@ -6,6 +6,7 @@ from stoobly_agent.config.constants.env_vars import ENV
|
|
6
6
|
DATA_DIR_NAME = '.stoobly'
|
7
7
|
DB_FILE_NAME = 'stoobly_agent.sqlite3'
|
8
8
|
DB_VERSION_NAME = 'VERSION'
|
9
|
+
MITMPROXY_OPTIONS_FILE_NAME = 'options.json'
|
9
10
|
|
10
11
|
class DataDir:
|
11
12
|
|
@@ -102,6 +103,35 @@ class DataDir:
|
|
102
103
|
def db_version_path(self):
|
103
104
|
return os.path.join(self.db_dir_path, DB_VERSION_NAME)
|
104
105
|
|
106
|
+
@property
|
107
|
+
def mitmproxy_options_json_path(self):
|
108
|
+
return os.path.join(self.tmp_dir_path, MITMPROXY_OPTIONS_FILE_NAME)
|
109
|
+
|
110
|
+
@property
|
111
|
+
def mitmproxy_conf_dir_path(self):
|
112
|
+
from stoobly_agent.config.constants.mitmproxy import DEFAULT_CONF_DIR_PATH
|
113
|
+
|
114
|
+
conf_dir = DEFAULT_CONF_DIR_PATH
|
115
|
+
|
116
|
+
options_json = self.mitmproxy_options_json_path
|
117
|
+
if os.path.exists(options_json):
|
118
|
+
try:
|
119
|
+
with open(options_json, 'r') as fp:
|
120
|
+
import json
|
121
|
+
contents = fp.read()
|
122
|
+
options = json.loads(contents)
|
123
|
+
_conf_dir = options.get('confdir')
|
124
|
+
|
125
|
+
if _conf_dir or not os.path.exists(_conf_dir):
|
126
|
+
conf_dir = _conf_dir
|
127
|
+
except Exception:
|
128
|
+
pass
|
129
|
+
|
130
|
+
if not os.path.exists(conf_dir):
|
131
|
+
os.makedirs(conf_dir)
|
132
|
+
|
133
|
+
return conf_dir
|
134
|
+
|
105
135
|
@property
|
106
136
|
def settings_file_path(self):
|
107
137
|
return os.path.join(self.path, 'settings.yml')
|
@@ -182,6 +212,9 @@ class DataDir:
|
|
182
212
|
if not os.path.exists(self.__data_dir_path):
|
183
213
|
os.mkdir(self.__data_dir_path)
|
184
214
|
|
215
|
+
# Create tmp folder
|
216
|
+
os.mkdir(os.path.join(self.__data_dir_path, 'tmp'))
|
217
|
+
|
185
218
|
with open(os.path.join(self.__data_dir_path, '.gitignore'), 'w') as fp:
|
186
219
|
fp.write(
|
187
220
|
"\n".join([
|
@@ -3,11 +3,9 @@ import os
|
|
3
3
|
import pdb
|
4
4
|
|
5
5
|
from stoobly_agent.config.data_dir import DataDir
|
6
|
-
from stoobly_agent.lib.logger import Logger
|
7
6
|
|
8
7
|
class MitmproxyConfig():
|
9
8
|
MITMPROXY_DIR_NAME = '.mitmproxy'
|
10
|
-
MITMPROXY_OPTIONS_FILE_NAME = 'options.json'
|
11
9
|
|
12
10
|
__instance = None
|
13
11
|
__master = None
|
@@ -18,7 +16,7 @@ class MitmproxyConfig():
|
|
18
16
|
else:
|
19
17
|
self.__master = master
|
20
18
|
|
21
|
-
self.__mitmproxy_dir_path =
|
19
|
+
self.__mitmproxy_dir_path = DataDir.instance().mitmproxy_conf_dir_path
|
22
20
|
|
23
21
|
if not os.path.exists(self.__mitmproxy_dir_path):
|
24
22
|
os.mkdir(self.__mitmproxy_dir_path)
|
@@ -46,7 +44,7 @@ class MitmproxyConfig():
|
|
46
44
|
def get(self, key: str):
|
47
45
|
if not self.__master:
|
48
46
|
try:
|
49
|
-
fp = open(
|
47
|
+
fp = open(DataDir.instance().mitmproxy_options_json_path, 'r')
|
50
48
|
contents = fp.read()
|
51
49
|
fp.close()
|
52
50
|
options = json.loads(contents)
|
@@ -66,10 +64,6 @@ class MitmproxyConfig():
|
|
66
64
|
if self.__master:
|
67
65
|
self.__master.options.set(*option)
|
68
66
|
|
69
|
-
@property
|
70
|
-
def options_json_path(self):
|
71
|
-
return os.path.join(DataDir.instance().tmp_dir_path, self.MITMPROXY_OPTIONS_FILE_NAME)
|
72
|
-
|
73
67
|
def dump(self):
|
74
68
|
if not self.__master:
|
75
69
|
return
|
@@ -78,6 +72,6 @@ class MitmproxyConfig():
|
|
78
72
|
for k, v in self.__master.options.items():
|
79
73
|
options[k] = v.current()
|
80
74
|
|
81
|
-
fp = open(
|
75
|
+
fp = open(DataDir.instance().mitmproxy_options_json_path, 'w')
|
82
76
|
fp.write(json.dumps(options, indent=2, sort_keys=True))
|
83
77
|
fp.close()
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import pdb
|
2
|
+
import pytest
|
3
|
+
|
4
|
+
from stoobly_agent.test.test_helper import reset
|
5
|
+
|
6
|
+
from stoobly_agent.config.constants.mitmproxy import DEFAULT_CONF_DIR_PATH
|
7
|
+
from stoobly_agent.config.data_dir import DataDir
|
8
|
+
from stoobly_agent.app.cli.helpers.certificate_authority import CertificateAuthority
|
9
|
+
|
10
|
+
@pytest.fixture(autouse=True, scope='module')
|
11
|
+
def settings():
|
12
|
+
return reset()
|
13
|
+
|
14
|
+
class TestCertificateAuthority():
|
15
|
+
|
16
|
+
@pytest.fixture(autouse=True, scope='class')
|
17
|
+
def certificate_authority(self):
|
18
|
+
return CertificateAuthority(DataDir.instance().certs_dir_path)
|
19
|
+
|
20
|
+
class TestWhenCertDirConfigured():
|
21
|
+
|
22
|
+
def test_defaults_to_not_installed(self, certificate_authority: CertificateAuthority):
|
23
|
+
assert not certificate_authority.certs_generated
|
24
|
+
|
25
|
+
def test_generates(self, certificate_authority: CertificateAuthority):
|
26
|
+
certificate_authority.generate_certs()
|
27
|
+
assert certificate_authority.certs_generated
|
28
|
+
|
29
|
+
class TestWhenCertDirNotConfigured():
|
30
|
+
|
31
|
+
@pytest.fixture(autouse=True, scope='class')
|
32
|
+
def certificate_authority(self):
|
33
|
+
return CertificateAuthority()
|
34
|
+
|
35
|
+
def test_defaults_to_mitmproxy_confdir(self, certificate_authority: CertificateAuthority):
|
36
|
+
assert certificate_authority.certs_dir == DEFAULT_CONF_DIR_PATH
|
37
|
+
|
38
|
+
class TestSign():
|
39
|
+
|
40
|
+
class TestWhenExistentHostname():
|
41
|
+
|
42
|
+
@pytest.fixture(scope='class')
|
43
|
+
def hostname(self):
|
44
|
+
return 'www.google.com'
|
45
|
+
|
46
|
+
@pytest.fixture(autouse=True, scope='class')
|
47
|
+
def sign(self, certificate_authority: CertificateAuthority, hostname: str):
|
48
|
+
certificate_authority.sign(hostname)
|
49
|
+
|
50
|
+
def test_signed(self, certificate_authority: CertificateAuthority, hostname: str):
|
51
|
+
assert certificate_authority.signed(hostname)
|
52
|
+
|
53
|
+
class TestWhenNonExistentHostname():
|
54
|
+
|
55
|
+
@pytest.fixture(scope='class')
|
56
|
+
def hostname(self):
|
57
|
+
return 'test.stoobly.com'
|
58
|
+
|
59
|
+
@pytest.fixture(autouse=True, scope='class')
|
60
|
+
def sign(self, certificate_authority: CertificateAuthority, hostname: str):
|
61
|
+
certificate_authority.sign(hostname)
|
62
|
+
|
63
|
+
def test_signed(self, certificate_authority: CertificateAuthority, hostname: str):
|
64
|
+
assert certificate_authority.signed(hostname)
|
65
|
+
|
66
|
+
class TestWhenSigned():
|
67
|
+
|
68
|
+
@pytest.fixture(scope='class')
|
69
|
+
def hostname(self):
|
70
|
+
return 'www.google.com'
|
71
|
+
|
72
|
+
@pytest.fixture(autouse=True, scope='class')
|
73
|
+
def sign(self, certificate_authority: CertificateAuthority, hostname: str):
|
74
|
+
certificate_authority.sign(hostname)
|
75
|
+
|
76
|
+
def test_it_does_not_sign(self, certificate_authority: CertificateAuthority, hostname: str):
|
77
|
+
assert not certificate_authority.sign(hostname)
|
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.5
|
@@ -5,10 +5,10 @@ import pytest
|
|
5
5
|
|
6
6
|
from stoobly_agent.config.constants.env_vars import ENV
|
7
7
|
from stoobly_agent.config.constants.mode import NONE
|
8
|
+
from stoobly_agent.config.constants.mitmproxy import DEFAULT_CONF_DIR_PATH
|
8
9
|
from stoobly_agent.config.data_dir import DataDir, DATA_DIR_NAME
|
9
10
|
from stoobly_agent.test.test_helper import reset
|
10
11
|
|
11
|
-
|
12
12
|
class TestDataDir():
|
13
13
|
@pytest.fixture(autouse=True, scope='class')
|
14
14
|
def settings(self):
|
@@ -85,3 +85,10 @@ class TestDataDir():
|
|
85
85
|
shutil.rmtree(temp_dir)
|
86
86
|
os.chdir(original_cwd)
|
87
87
|
|
88
|
+
class TestMitmproxyConfDirPath():
|
89
|
+
@pytest.fixture(autouse=True, scope='class')
|
90
|
+
def settings(self):
|
91
|
+
return reset()
|
92
|
+
|
93
|
+
def test_default_path(self):
|
94
|
+
assert DataDir.instance().mitmproxy_conf_dir_path == DEFAULT_CONF_DIR_PATH
|
@@ -1,4 +1,4 @@
|
|
1
|
-
stoobly_agent/__init__.py,sha256=
|
1
|
+
stoobly_agent/__init__.py,sha256=5X3amtha1fHxFEqgS7QTUVOXpD2fYkqLNspMcHbm1Mk,44
|
2
2
|
stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
stoobly_agent/app/api/__init__.py,sha256=ctkB8KR-eXO0SFhj602huHiyvQ3PslFWd8fkcufgrAI,1000
|
4
4
|
stoobly_agent/app/api/application_http_request_handler.py,sha256=jf4fkqjOiCeI2IM5Ro7ie0v_C6y0-7-5TIE_IKMPOfg,5513
|
@@ -17,8 +17,7 @@ stoobly_agent/app/api/scenarios_controller.py,sha256=zJViyme9l36mlJDQONRu4N-A0kd
|
|
17
17
|
stoobly_agent/app/api/simple_http_request_handler.py,sha256=ZdJ4kKEcPDpck74dJu--M7zw76wQzpo_AQArZwN9Bes,4466
|
18
18
|
stoobly_agent/app/api/statuses_controller.py,sha256=gQsl28h-3mZ_bByEHpc4mRy3KFvUz5PeAgVB0cPx_Ao,1791
|
19
19
|
stoobly_agent/app/cli/__init__.py,sha256=uS4KJgMAhm0JVnEUC-ONW-Bq6A8I9v7Fk-_Ka7gXlrU,470
|
20
|
-
stoobly_agent/app/cli/ca_cert_cli.py,sha256=
|
21
|
-
stoobly_agent/app/cli/ca_cert_installer.py,sha256=8bKW5SyPBHC1Luck_t14sJRvZYEvIP6zzH3GQ1lK70M,2252
|
20
|
+
stoobly_agent/app/cli/ca_cert_cli.py,sha256=QXl01nyCQLhiGr8vozZicui36AlgHU1q_V9c_w_xY-Q,1444
|
22
21
|
stoobly_agent/app/cli/config_cli.py,sha256=vgik4PwqKdq1cQ4BvmeyXTmh-LQZpe2PUkNIqblzP2E,15658
|
23
22
|
stoobly_agent/app/cli/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
23
|
stoobly_agent/app/cli/decorators/config.py,sha256=AWrDGZm_gjCWFYlRwdla3iE6H7OSlM4FxkaXRNovBZA,2428
|
@@ -29,6 +28,7 @@ stoobly_agent/app/cli/feature_cli.py,sha256=d-MUTbK3gbZxZNJUjM43fioZyhPyzetQNKjI
|
|
29
28
|
stoobly_agent/app/cli/handlers/request_cli_handler.py,sha256=IPToK9OfiDda-mkq8HpiSSqK2m-UR15-8f8LVoLAHTA,6965
|
30
29
|
stoobly_agent/app/cli/handlers/scenario_cli_handler.py,sha256=1KSsfhR1mmPUCmhjzWTjf_L9NcQglLvVK3KURrPt92A,7425
|
31
30
|
stoobly_agent/app/cli/helpers/__init__.py,sha256=26ODsQi5NoJPwq4Ya7_lmtRalhhXOr_jfdZQWGLy6Gc,161
|
31
|
+
stoobly_agent/app/cli/helpers/certificate_authority.py,sha256=d9hW_p3Uvnh5VAE0pCQMRTH77ihON7a-qUMCTbjOaqI,7178
|
32
32
|
stoobly_agent/app/cli/helpers/context.py,sha256=Jv97ueNnqea5UDm57VOUiQSHKWBeNlCcrq5-qW2tcwQ,1624
|
33
33
|
stoobly_agent/app/cli/helpers/endpoint_facade.py,sha256=_1TR9PcRnqpl9Q4njFHMM_Btqpr6cCf757FFcywa3oU,2318
|
34
34
|
stoobly_agent/app/cli/helpers/endpoints_apply_context.py,sha256=PvUzJLXNKl6wIQj2q8YZK1MAm1yqzDLLlpCYMoyWb9I,1831
|
@@ -67,13 +67,13 @@ stoobly_agent/app/cli/project_cli.py,sha256=EXjeLjbnq9PhfCjvyfZ0UnJ2tejeCS0SIAo3
|
|
67
67
|
stoobly_agent/app/cli/report_cli.py,sha256=ZxJw0Xkx7KFZJn9e45BSKRKon8AD0Msrwy1fbPfbv0c,2543
|
68
68
|
stoobly_agent/app/cli/request_cli.py,sha256=THNloW111l9MLE0oPg4y7hVXL1U7OXoz760g0A1CtJ0,7747
|
69
69
|
stoobly_agent/app/cli/scaffold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
70
|
-
stoobly_agent/app/cli/scaffold/app.py,sha256=
|
70
|
+
stoobly_agent/app/cli/scaffold/app.py,sha256=y9es1fDnYwQ3s-Pp58KvSgkt8Chn7BTq16GBG5RmYpY,3555
|
71
71
|
stoobly_agent/app/cli/scaffold/app_command.py,sha256=o6WV6tcMGBWJsPFdV2DD-m1Dq0cy3Q3SESokaU5hgp4,2061
|
72
72
|
stoobly_agent/app/cli/scaffold/app_config.py,sha256=ZAHGovwxFy3zICfusj9_gUC_FBTIoB7KbPbQaGK8x1c,573
|
73
73
|
stoobly_agent/app/cli/scaffold/app_create_command.py,sha256=G4q-arXJH0uf6kagiWk26VqKG4zpOngxvJappxIkt-0,568
|
74
74
|
stoobly_agent/app/cli/scaffold/command.py,sha256=klpsgL6V7b2DRHpN33pl-QlmntDkvDO_dsF5JziYnL8,245
|
75
75
|
stoobly_agent/app/cli/scaffold/config.py,sha256=HZU5tkvr3dkPr4JMXZtrJlu2wxxO-134Em6jReFFcq0,688
|
76
|
-
stoobly_agent/app/cli/scaffold/constants.py,sha256=
|
76
|
+
stoobly_agent/app/cli/scaffold/constants.py,sha256=SMuCd0FwX_C4wENUJU-AxW0KvEsg_1MhQ4GkU287B3Y,1286
|
77
77
|
stoobly_agent/app/cli/scaffold/docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
78
|
stoobly_agent/app/cli/scaffold/docker/app_builder.py,sha256=IsXZFUumWEBA8NYDFdAohuKmxocoqHqlVFg0-E92QLI,621
|
79
79
|
stoobly_agent/app/cli/scaffold/docker/builder.py,sha256=lNOvDxoLB1L8KSDrvoG4CUuWLAM2egLxwOWeV9Tf8H8,2832
|
@@ -98,8 +98,8 @@ stoobly_agent/app/cli/scaffold/service_workflow.py,sha256=sQ_Edy_wGHKMXpD0DmhnOW
|
|
98
98
|
stoobly_agent/app/cli/scaffold/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
99
99
|
stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=1Zm5_ROKZzFMAkMt8dluEJASI2CBi9d9If_Gch9FMNM,193
|
100
100
|
stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.proxy,sha256=JfZOVALEmRE_hVEMIHTmem9Ln32qnfKOAUO1c5Z9fqw,1027
|
101
|
-
stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=
|
102
|
-
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=
|
101
|
+
stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=JOtlDYzba0phpQdsKcWPbJCS9ZWgoqixq7wwzKTVzj0,4161
|
102
|
+
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=LNNuygX6hMtmElxaO6jTIYq3psYmxNv7xax7ra6JzO4,407
|
103
103
|
stoobly_agent/app/cli/scaffold/templates/app/Makefile,sha256=t7xRaCc9q3k7W34S0h9KX02GljSVwGpPnFmiLUvWxsw,80
|
104
104
|
stoobly_agent/app/cli/scaffold/templates/app/build/.config.yml,sha256=8Wt8ZZ5irvBYYS44xGrR_EWlZDuXH9kyWmquzsh7s8g,19
|
105
105
|
stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml,sha256=WI1tE97QDXif1qGCyFbKWNw_2VhNvbBTT2mtwW6C-RI,270
|
@@ -143,12 +143,13 @@ stoobly_agent/app/cli/scaffold/templates/app/gateway/.docker-compose.base.yml,sh
|
|
143
143
|
stoobly_agent/app/cli/scaffold/templates/app/gateway/mock/.docker-compose.mock.yml,sha256=LiMgBvOozkBH34-72kQXLylmrnERwjQfs8aws6BlZHM,232
|
144
144
|
stoobly_agent/app/cli/scaffold/templates/app/gateway/record/.docker-compose.record.yml,sha256=4pwMuBdfaf9hX1RVJ3KU3FY4E5BXYSPFsfUGKmiuXPk,234
|
145
145
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.config.yml,sha256=XnLQZMzzMMIwVycjyPN5QXsmRztkTFAna1kIHYuDfJQ,19
|
146
|
-
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml,sha256=
|
146
|
+
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml,sha256=GO3dIJ1WlazBDik_PDo1YvrSXcqeT5MMJjbb_iBdH6g,106
|
147
147
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.exec.yml,sha256=rPc88-E1xoRwaqEVGVuTfOe7BcKCdDtatVqhNNIrhN8,299
|
148
148
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.create,sha256=EZe84bLAKB-mrB9PGMx-amyMjp6x-ESUZKC8wxrWdLE,245
|
149
149
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.delete,sha256=RspRDQ7WT5jpN2o-6qXOlH-A2VpN4981pD4ZJljk9Rw,260
|
150
150
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.disable,sha256=xVf4Pk1RLvJm7Ff0rbGoWhYHPv0ME5e93fxS2yFqLnE,45
|
151
151
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.enable,sha256=sfUSPG4uHdXX95BLgivXQYLbsLBP2DjJIiSTXRtvXuY,188
|
152
|
+
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.mkcert,sha256=vyHaXmvy-7oL2RD8rIxwT-fdJS5kXmB0yHK5fRMo_cM,46
|
152
153
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.reset,sha256=_pvbLk-ektsCHEh_egDtd6c0sVqCPunwUWggarfoli0,238
|
153
154
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.run,sha256=AporhoTO76u9lexoPJ9XnFsjRnhpPRZdv1g2ri3-V50,210
|
154
155
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.snapshot,sha256=Kav1QNhKG7f0DBzc4-9dmxZMmmVTxmPqakT_W4kVYKk,241
|
@@ -176,13 +177,13 @@ stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init,sha256=YxWVVQMEd
|
|
176
177
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures/.keep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
177
178
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures.yml,sha256=8DW2LN5WYg1hxmZkqr8o5dxLgDxHtBARFSUiNjz009Y,226
|
178
179
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/lifecycle_hooks.py,sha256=U7mlzT_wBR3uhHSG6CAyt5tBUNAvdIrCw33gdB-F294,467
|
179
|
-
stoobly_agent/app/cli/scaffold/workflow.py,sha256=
|
180
|
+
stoobly_agent/app/cli/scaffold/workflow.py,sha256=5Q0km8-QAylWOUxOtY2D-h0HlDWB8PmtSEYWLA6bLeo,828
|
180
181
|
stoobly_agent/app/cli/scaffold/workflow_command.py,sha256=V4Wfv9bgh5MlWAaW_68uudC1wsMcvcTogjZ7mvKDD74,3308
|
181
182
|
stoobly_agent/app/cli/scaffold/workflow_copy_command.py,sha256=R9hh5dWVz7vGld7pENAA_a9gW56EkgG2K35nBQYXwyI,1462
|
182
183
|
stoobly_agent/app/cli/scaffold/workflow_create_command.py,sha256=5xnRYrVb2KlSDARZFjLGGWeURXRu63OHoRLEhO-EAvw,3401
|
183
184
|
stoobly_agent/app/cli/scaffold/workflow_log_command.py,sha256=FN-XyMVnulbassBuqeB21J_PQnOsr_LNs0Dg2tcDkjg,435
|
184
|
-
stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=
|
185
|
-
stoobly_agent/app/cli/scaffold_cli.py,sha256=
|
185
|
+
stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=AAiUKJ3Wd_Za1_AIKT_F5fvd4RYOMVNFOTNqMGJVbQI,4220
|
186
|
+
stoobly_agent/app/cli/scaffold_cli.py,sha256=jk2RdQfd46FOPfOZZbz-jNEu-AXG5sauKYmox7arCaA,16586
|
186
187
|
stoobly_agent/app/cli/scenario_cli.py,sha256=3J1EiJOvunkfWrEkOsanw-XrKkOk78ij_GjBlE9p7CE,8229
|
187
188
|
stoobly_agent/app/cli/snapshot_cli.py,sha256=XNxpTm5z3bnBGNGI3_XZIif0ypN62WqYfDmShL5fXg4,9965
|
188
189
|
stoobly_agent/app/cli/trace_cli.py,sha256=K7E-vx3JUcqEDSWOdIOi_AieKNQz7dBfmRrVvKDkzFI,4605
|
@@ -328,7 +329,7 @@ stoobly_agent/app/proxy/replay/alias_resolver.py,sha256=0t7dISfVoFIa_fOqhUZQr0Ib
|
|
328
329
|
stoobly_agent/app/proxy/replay/body_parser_service.py,sha256=ZoWEN9qdo8nMyWSM-YDeqD3YVybYGMk7EuT2qdguJ6I,3621
|
329
330
|
stoobly_agent/app/proxy/replay/context.py,sha256=_dtU2-OmTVY_zsXRU7gdL9E7zbx9a9jSfkm9_H6vfp0,796
|
330
331
|
stoobly_agent/app/proxy/replay/multipart.py,sha256=_jukCaTEuuUi5LhP_DCsEHupzGBUZiZyb51ed1rarm8,2249
|
331
|
-
stoobly_agent/app/proxy/replay/replay_request_service.py,sha256=
|
332
|
+
stoobly_agent/app/proxy/replay/replay_request_service.py,sha256=ZxSD_7DhGgqS-BGUTpVgjLmE5O5qUwv-kOPSu6VEBkA,7045
|
332
333
|
stoobly_agent/app/proxy/replay/replay_scenario_service.py,sha256=9jV-iO5EBg8geUblEtjjWRFIkom_Pqmo7P-lTc3S4Xw,2824
|
333
334
|
stoobly_agent/app/proxy/replay/rewrite_params_service.py,sha256=jEHlT6_OHq_VBa09Hd6QaRyErv7tZnziDvW7m3Q8CQg,2234
|
334
335
|
stoobly_agent/app/proxy/replay/trace_context.py,sha256=lKpnQWVCUTcMPE-SOi90za3U1yL0VeBlIgj_KP63VsE,10435
|
@@ -380,7 +381,7 @@ stoobly_agent/app/settings/intercept_settings.py,sha256=K5dY1y0nBcBMIVQzrIDYCgou
|
|
380
381
|
stoobly_agent/app/settings/match_rule.py,sha256=8zNs8HybGaQrZQGo2EVfrlv_lqFHMgNpwkXRx_DIy9c,812
|
381
382
|
stoobly_agent/app/settings/match_settings.py,sha256=QmFj79_twafbkyu2F20SOJ0guXBURiWgGIMkxItXumM,772
|
382
383
|
stoobly_agent/app/settings/parameter_rule.py,sha256=UmmC73RY1c7PJ024wEh3IVm3veaDyHohov2OigdKpYI,881
|
383
|
-
stoobly_agent/app/settings/proxy_settings.py,sha256=
|
384
|
+
stoobly_agent/app/settings/proxy_settings.py,sha256=7sWQqRqpgOBlav2TI8KWS4bjR_JhX0q_Vv2sW0rdDLE,1853
|
384
385
|
stoobly_agent/app/settings/remote_settings.py,sha256=z2q0i2hGNulonfpOZcBUwikNo_EEA5ChbOWumzzmqWc,1960
|
385
386
|
stoobly_agent/app/settings/rewrite_rule.py,sha256=qECIEtZUOSww1Zsfmwh-L-mx7rFZWEuaPWfJ3faTlM4,1416
|
386
387
|
stoobly_agent/app/settings/rewrite_settings.py,sha256=Ta8uk7usMZ9jgMg71ZNwUj9rQ1Cb8G9d--0lAQ-z7OQ,851
|
@@ -391,7 +392,7 @@ stoobly_agent/app/settings/types/remote_settings.py,sha256=4PvEGKULXM0zv29XTDzV7
|
|
391
392
|
stoobly_agent/app/settings/types/ui_settings.py,sha256=BqPy2F32AbODqzi2mp2kRk28QVUydQIwVmvftn46pco,84
|
392
393
|
stoobly_agent/app/settings/ui_settings.py,sha256=YDEUMPuJFh0SLHaGz6O-Gpz8nwsunNzeuc-TzO9OUbM,1170
|
393
394
|
stoobly_agent/app/settings/url_rule.py,sha256=Qx7YrIpVRSC-4LeNiCAfCtE50Jou4423hojMW-4qUYg,954
|
394
|
-
stoobly_agent/cli.py,sha256=
|
395
|
+
stoobly_agent/cli.py,sha256=4WLcWPHWdv5dZEitXtDj6_I5AH5WVvQqE_uDe6bPEno,9906
|
395
396
|
stoobly_agent/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
396
397
|
stoobly_agent/config/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
397
398
|
stoobly_agent/config/constants/alias_resolve_strategy.py,sha256=_R1tVqFnyGxCraVS5-dhSskaDj_X8-NthsY7i_bEt9M,119
|
@@ -399,6 +400,7 @@ stoobly_agent/config/constants/custom_headers.py,sha256=90qrR2DeaoPQvxCBf_QLVFrD
|
|
399
400
|
stoobly_agent/config/constants/env_vars.py,sha256=HAR_ZIdXXbpWQgCDaRR5RtpVyGXCsMLr_Fh8n6S12K0,1344
|
400
401
|
stoobly_agent/config/constants/headers.py,sha256=fm5FxKroCpNHTaKMjrIO4npNmT-eAUplXt_ix0N5Ruo,223
|
401
402
|
stoobly_agent/config/constants/lifecycle_hooks.py,sha256=aobclZmcND_mUnFKkUgpxgwd5EU3pjgAvB-NFf2dCgE,846
|
403
|
+
stoobly_agent/config/constants/mitmproxy.py,sha256=AhaxRGMb7OC4DBqNvyBP0OVQSByCeA-itefzmMoIS14,116
|
402
404
|
stoobly_agent/config/constants/mock_policy.py,sha256=KrRVPJYRSb0hIH9fjAV9aQ8OsnhnYo9l7dylug62YVg,41
|
403
405
|
stoobly_agent/config/constants/mode.py,sha256=J35vV1LPQRhD2ob6DaJ7mLGJrMhv-CVnC6IHWd7b_GM,160
|
404
406
|
stoobly_agent/config/constants/record_policy.py,sha256=2DZzMMuMSCH9ZEfQDpPQ7fLEiCz7BNLITzCQ7Ti39BQ,89
|
@@ -408,8 +410,8 @@ stoobly_agent/config/constants/statuses.py,sha256=dR88qHHnDR_5ppuKijwc0R24D_fWc6
|
|
408
410
|
stoobly_agent/config/constants/test_filter.py,sha256=rQ-eMZ8kpq49pqS1PQhaRx59dlRicd-Ag4sjgoXfpY8,109
|
409
411
|
stoobly_agent/config/constants/test_output_level.py,sha256=yrn8cs-lexrK0Ckpc_SX3NhsZPfVhNpmjzjSP62LCro,135
|
410
412
|
stoobly_agent/config/constants/test_strategy.py,sha256=d6-WgKqElB3q_csP-QYFkhR4XvlPGfGdCJ-SBEDEoL4,152
|
411
|
-
stoobly_agent/config/data_dir.py,sha256=
|
412
|
-
stoobly_agent/config/mitmproxy.py,sha256=
|
413
|
+
stoobly_agent/config/data_dir.py,sha256=DT3bXdOEQPKKIxOX1zpBWHePYpHskUjia-hDT9BUCb4,7466
|
414
|
+
stoobly_agent/config/mitmproxy.py,sha256=z9L1JSWwQFsn4gMZk8Dcz5mOcUc6dYnW1ZugrYTEttw,1902
|
413
415
|
stoobly_agent/config/schema.yml,sha256=b4I8utMrUrMSr86UbXgYYxt2-TJynQA9oQtOQ7CM-PE,1963
|
414
416
|
stoobly_agent/config/settings.yml.sample,sha256=muxP1bWrtknVaSgdseFaw23tuwduILNdQTVy7aB519M,404
|
415
417
|
stoobly_agent/config/source_dir.py,sha256=3oYwWmIrid0YsIf91AIUI2itAA3pdPA10eHaC-nmelQ,1146
|
@@ -633,6 +635,7 @@ stoobly_agent/test/app/cli/config/project/config_project_set_test.py,sha256=bV2O
|
|
633
635
|
stoobly_agent/test/app/cli/config/rewrite_test.py,sha256=Y1zSTJJK4Q8Ac8qqHbKt-kFraBIwJIpr29_7V81OTy0,5601
|
634
636
|
stoobly_agent/test/app/cli/config/scenario/config_scenario_set_test.py,sha256=1A5cYH3OdwtRwTmDETg0af9urHA4K6VVeA76zub2dnQ,2525
|
635
637
|
stoobly_agent/test/app/cli/endpoint/endpoint_cli_apply_test.py,sha256=cW5RDd-spyEknV1krFv37GHgWShPVOgvPyX2jgwnWZw,3886
|
638
|
+
stoobly_agent/test/app/cli/helpers/certificate_authority_test.py,sha256=Y5YkwVFKyZBbfWK-vZVo3__jzRlRDOCFNvTLbDpjGg4,2639
|
636
639
|
stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_additional_props_test.py,sha256=t4Wggky_fexBrRqvK12Qgj71UKsM0gGqutOEfAdOqtk,1772
|
637
640
|
stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_info_test.py,sha256=qA6PJEtv1tS7Ksl6R7GjbIS_f9SxINPr8SrcXTuG-WU,3027
|
638
641
|
stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_oauth2_scopes_test.py,sha256=Gw_YtZR2EKy09jAJEGQ4phrAoHlzlqhbJ9bLH91UrRU,3143
|
@@ -673,7 +676,7 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
|
|
673
676
|
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=5IFGVGn3ogvRT0fzZgXzhrYlSfSlr8IR1p_XExaegMQ,3399
|
674
677
|
stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
|
675
678
|
stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
|
676
|
-
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=
|
679
|
+
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=nXE_EpedYoeSULwKIDXj71cxWsQlHoUBnSHHXtcmlyA,5
|
677
680
|
stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
|
678
681
|
stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
|
679
682
|
stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
|
@@ -693,7 +696,7 @@ stoobly_agent/test/app/test/matchers/diff_test.py,sha256=fNbzAz3MptieVIrMInxGlNa
|
|
693
696
|
stoobly_agent/test/app/test/matchers/fuzzy_test.py,sha256=wkySpK00dFZLlXmW7xE7Q6UPJYLiq8aCnIHt4Nm4WEk,3231
|
694
697
|
stoobly_agent/test/cli/mock_test.py,sha256=Y3E_I4FnDf3aEOl1dDABC6x571iBgZuINE5giTH5Lcs,4566
|
695
698
|
stoobly_agent/test/cli/record_test.py,sha256=UOR6KyN8FHRdo2k4ybC2OTKiMNg6Gns-A0qA4CeqlKI,13188
|
696
|
-
stoobly_agent/test/config/data_dir_test.py,sha256=
|
699
|
+
stoobly_agent/test/config/data_dir_test.py,sha256=sszUW6MKGFCS9JGhYL-E91hBzfI2jMaHRJMifNjZ00M,2659
|
697
700
|
stoobly_agent/test/mock_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
698
701
|
stoobly_agent/test/mock_data/endpoint_show_response.py,sha256=JFEkceHI6nH38zitCbFMozE1lODahxmvj70N5eM_sec,5901
|
699
702
|
stoobly_agent/test/mock_data/petstore-additional-props.yaml,sha256=CU5363OV1gFS9nC-oa6sRHncSQy16CF90kBalkCySQA,1512
|
@@ -707,8 +710,8 @@ stoobly_agent/test/mock_data/petstore.yaml,sha256=CCdliJky04Az4FIOkFA883uunwFDHL
|
|
707
710
|
stoobly_agent/test/mock_data/request_show_response.py,sha256=K_a0fP0QT58T8sX9PaM6hqtX1A1depZsqg_GsNPf--k,707
|
708
711
|
stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
|
709
712
|
stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
|
710
|
-
stoobly_agent-1.0.
|
711
|
-
stoobly_agent-1.0.
|
712
|
-
stoobly_agent-1.0.
|
713
|
-
stoobly_agent-1.0.
|
714
|
-
stoobly_agent-1.0.
|
713
|
+
stoobly_agent-1.0.6.dist-info/LICENSE,sha256=8QKGyy45eN76Zk52h8gu1DKX2B_gbWgZ3nzDLofEbaE,548
|
714
|
+
stoobly_agent-1.0.6.dist-info/METADATA,sha256=LLWexqmnANwAeXsSBtLhxepnsykianH5kVk8lmugnE8,3388
|
715
|
+
stoobly_agent-1.0.6.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
716
|
+
stoobly_agent-1.0.6.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
|
717
|
+
stoobly_agent-1.0.6.dist-info/RECORD,,
|
@@ -1,54 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import subprocess
|
3
|
-
import sys
|
4
|
-
|
5
|
-
class CACertInstaller():
|
6
|
-
|
7
|
-
def __init__(self):
|
8
|
-
home_dir = os.path.expanduser('~')
|
9
|
-
self.mitmproxy_certs_dir = os.path.join(home_dir, '.mitmproxy')
|
10
|
-
self.pem_file_name = 'mitmproxy-ca-cert.pem'
|
11
|
-
self.cer_file_name = 'mitmproxy-ca-cert.cer'
|
12
|
-
self.crt_file_name = 'mitmproxy-ca-cert.crt'
|
13
|
-
|
14
|
-
@property
|
15
|
-
def mitm_crt_absolute_path(self):
|
16
|
-
return os.path.join(self.mitmproxy_certs_dir, self.crt_file_name)
|
17
|
-
|
18
|
-
# https://askubuntu.com/a/94861
|
19
|
-
def handle_debian(self):
|
20
|
-
extra_ca_certs_dir = '/usr/local/share/ca-certificates/extra'
|
21
|
-
|
22
|
-
mitm_cer_absolute_path = os.path.join(self.mitmproxy_certs_dir, self.cer_file_name)
|
23
|
-
self.__ensure_exists(mitm_cer_absolute_path)
|
24
|
-
|
25
|
-
extra_crt_absolute_path = os.path.join(extra_ca_certs_dir, self.crt_file_name)
|
26
|
-
|
27
|
-
subprocess.run(f"sudo mkdir -p {extra_ca_certs_dir}".split(), check=True)
|
28
|
-
subprocess.run(f"sudo cp {mitm_cer_absolute_path} {extra_crt_absolute_path}".split(), check=True)
|
29
|
-
subprocess.run("sudo update-ca-certificates".split(), check=True)
|
30
|
-
|
31
|
-
# https://www.dssw.co.uk/reference/security.html
|
32
|
-
def handle_darwin(self):
|
33
|
-
system_keychain_path = '/Library/Keychains/System.keychain'
|
34
|
-
|
35
|
-
mitm_pem_absolute_path = os.path.join(self.mitmproxy_certs_dir, self.pem_file_name)
|
36
|
-
self.__ensure_exists(mitm_pem_absolute_path)
|
37
|
-
|
38
|
-
subprocess.run(f"sudo security add-trusted-cert -d -p ssl -p basic -k {system_keychain_path} {mitm_pem_absolute_path}".split(), check=True)
|
39
|
-
|
40
|
-
def handle_rhel(self):
|
41
|
-
ca_trust_dir = '/etc/pki/ca-trust/source/anchors'
|
42
|
-
|
43
|
-
mitm_cer_absolute_path = os.path.join(self.mitmproxy_certs_dir, self.cer_file_name)
|
44
|
-
self.__ensure_exists(mitm_cer_absolute_path)
|
45
|
-
|
46
|
-
ca_trust_crt_absolute_path = os.path.join(ca_trust_dir, self.crt_file_name)
|
47
|
-
|
48
|
-
subprocess.run(f"sudo cp {mitm_cer_absolute_path} {ca_trust_crt_absolute_path}".split(), check=True)
|
49
|
-
subprocess.run("sudo update-ca-trust extract".split(), check=True)
|
50
|
-
|
51
|
-
def __ensure_exists(self, file_path):
|
52
|
-
if not os.path.exists(file_path):
|
53
|
-
print("Error: Proxy has not been started", file=sys.stderr)
|
54
|
-
sys.exit(1)
|
File without changes
|
File without changes
|