stoobly-agent 1.0.5__py3-none-any.whl → 1.0.7__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.
Files changed (27) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/cli/ca_cert_cli.py +29 -18
  3. stoobly_agent/app/cli/helpers/certificate_authority.py +201 -0
  4. stoobly_agent/app/cli/scaffold/app.py +29 -1
  5. stoobly_agent/app/cli/scaffold/constants.py +1 -0
  6. stoobly_agent/app/cli/scaffold/templates/app/.Makefile +13 -19
  7. stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +8 -5
  8. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +1 -1
  9. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.mkcert +3 -0
  10. stoobly_agent/app/cli/scaffold/workflow.py +1 -11
  11. stoobly_agent/app/cli/scaffold/workflow_run_command.py +12 -6
  12. stoobly_agent/app/cli/scaffold_cli.py +51 -9
  13. stoobly_agent/app/proxy/replay/replay_request_service.py +3 -2
  14. stoobly_agent/app/settings/proxy_settings.py +0 -1
  15. stoobly_agent/cli.py +2 -2
  16. stoobly_agent/config/constants/mitmproxy.py +4 -0
  17. stoobly_agent/config/data_dir.py +33 -0
  18. stoobly_agent/config/mitmproxy.py +3 -9
  19. stoobly_agent/test/app/cli/helpers/certificate_authority_test.py +77 -0
  20. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  21. stoobly_agent/test/config/data_dir_test.py +8 -1
  22. {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.7.dist-info}/METADATA +2 -2
  23. {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.7.dist-info}/RECORD +26 -23
  24. {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.7.dist-info}/WHEEL +1 -1
  25. stoobly_agent/app/cli/ca_cert_installer.py +0 -54
  26. {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.7.dist-info}/LICENSE +0 -0
  27. {stoobly_agent-1.0.5.dist-info → stoobly_agent-1.0.7.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.5'
2
+ VERSION = '1.0.7'
@@ -1,8 +1,10 @@
1
-
2
1
  import click
3
- import distro
2
+ import pdb
3
+ import sys
4
+
5
+ from stoobly_agent.config.data_dir import DataDir
4
6
 
5
- from .ca_cert_installer import CACertInstaller
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
- distro_name = distro.name(pretty=True)
18
-
19
- installer = CACertInstaller()
20
-
21
- # Ubuntu or other Debian based
22
- if distro.like() == 'debian':
23
- print(f"Installing CA certificate for {distro_name}...")
24
- installer.handle_debian()
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.__certs_dir_path = os.path.join(data_dir.tmp_dir_path, 'certs')
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,6 +1,7 @@
1
1
  from typing import Literal
2
2
 
3
3
  APP_NETWORK_ENV = 'APP_NETWORK'
4
+ CA_CERTS_DIR_ENV = 'CA_CERTS_DIR'
4
5
  CERTS_DIR_ENV = 'CERTS_DIR'
5
6
  COMPOSE_TEMPLATE = '.docker-compose.{workflow}.yml'
6
7
  CONFIG_FILE = '.config.yml'
@@ -1,8 +1,9 @@
1
1
  # Overridable Environment Variables
2
2
  #
3
3
  # STOOBLY_APP_DIR: path to the application source code directory
4
- # STOOBLY_CONTEXT_DIR: path to the folder containing the .stoobly folder
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
- run_script=$(app_data_dir)/tmp/run.sh
38
- run_env=export APP_DIR="$(app_dir)" && export CERTS_DIR="$(certs_dir)" && export CONTEXT_DIR="$(context_dir)"
39
- workflow_run=$(run_env) && $(source_env) && bash "$(run_script)"
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
- cert:
42
- if [ -z "$$(which mkcert)" ]; then \
43
- echo "Error: missing mkcert command"; \
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
- volumes:
15
- - ${CONTEXT_DIR}/.stoobly:/home/stoobly/.stoobly
17
+ extends:
18
+ service: context_base
@@ -2,4 +2,4 @@ services:
2
2
  stoobly_ui.base:
3
3
  extends:
4
4
  file: ../.docker-compose.base.yml
5
- service: context_base
5
+ service: ui_base
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ stoobly-agent scaffold app mkcert
@@ -32,17 +32,7 @@ class Workflow():
32
32
 
33
33
  @property
34
34
  def service_paths(self):
35
- services_dir = os.path.join(self.app.scaffold_dir_path, self.app.namespace)
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
- data_dir = DataDir.instance()
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
- def service_create_options(f):
72
- def wrapper(*args, **kwargs):
73
- return f(*args, **kwargs)
74
- return wrapper
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().certs_dir_path, help='Path to certs directory.')
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(kwargs['app_dir_path'], DOCKER_NAMESPACE, skip_validate_path=kwargs['dry_run'])
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(WARNING).warn(f"Service(s) {','.join(missing_services)} are not found")
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 kwargs['filter']:
395
+ if WORKFLOW_CUSTOM_FILTER not in filter:
354
396
  services += CORE_SERVICES
355
397
  else:
356
- if WORKFLOW_CUSTOM_FILTER in kwargs['filter']:
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.ca_cert_installer import CACertInstaller
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': CACertInstaller().mitm_crt_absolute_path if request_config['verify'] else False,
139
+ 'verify': ca.ca_cert_path('.crt') if request_config['verify'] else False,
139
140
  }
140
141
  )
141
142
  received_at = time()
@@ -1,4 +1,3 @@
1
- from nis import match
2
1
  import os
3
2
 
4
3
  from stoobly_agent.config.constants import env_vars
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=os.path.join(os.path.expanduser('~'), '.mitmproxy'), help='Location of the default mitmproxy configuration files.')
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).
@@ -0,0 +1,4 @@
1
+ import os
2
+
3
+ CONF_DIR_NAME = '.mitmproxy'
4
+ DEFAULT_CONF_DIR_PATH = os.path.join(os.path.expanduser('~'), CONF_DIR_NAME)
@@ -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 and os.path.exists(_conf_dir):
126
+ conf_dir = _conf_dir
127
+ except Exception:
128
+ pass
129
+ else:
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 = os.path.join(os.path.expanduser('~'), self.MITMPROXY_DIR_NAME)
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(self.options_json_path, 'r')
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(self.options_json_path, 'w')
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.4
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,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: stoobly-agent
3
- Version: 1.0.5
3
+ Version: 1.0.7
4
4
  Summary: Record, mock, and test HTTP(s) requests. CLI agent for Stoobly
5
5
  License: Apache-2.0
6
6
  Author: Matt Le
@@ -1,4 +1,4 @@
1
- stoobly_agent/__init__.py,sha256=6tvkXcNE_sNjUAP2DAWOB3-aEqTtRoqoAFjoATyoFdM,44
1
+ stoobly_agent/__init__.py,sha256=wSw9-Gxd6f7Hco-PFFshn84Ed5NXRZzPr_pJdTrL_eY,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=YKk7XAkDKL1edmDg3iqcBC1PAOrvOs2oJjWbmQEsaJE,859
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=iyNiQ54MYH8X_GNMfi2r3C8hr9zjKQm_cy3tAmbSRZs,2824
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=u6uUThiypT3hK4rRhabYEet1FY_oKlsIkXvDvRC4KCQ,1252
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=di-KeT_8vHlz86wqJeEx_jwbJrC3bSK_RHgJCGYrlpg,4114
102
- stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=jDEG7TkEyHhlt33H5JC-jD0nwdFk2rsDcaS6N9yjYRk,359
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=bxrtZqf3YtaJCukzScslh5PgWC5q8xkGIP1wKJf33LA,111
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=j2ihY_c7HPh275-awWARcsf1u4O2R1hhCXScLmO_Kn8,1096
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=quPMwahE7TQozbzntIQJRoEhNjv0e_QfpYZnbWQV1iE,4025
185
- stoobly_agent/app/cli/scaffold_cli.py,sha256=5facvX1S-SnJWWe644_8bBV4ytLQlj25RYDiiselvcU,14765
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=1OyrGb9Vfflhcfy4GBDIwa8iP5_7v3Cr7Q0zi6U9apc,7013
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=5Zmpmz6x0HpD1mSuoLpLd5vy67sqt-xEbm5PtHg8IvU,1875
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=cZJDCVa7PGGfiqg4GxaYn74-uPtQQAexHeWmGybw3YE,9915
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=8VUPD1HPGdOdfYJzjT2U_HQFsu5S2b85iroCTUYIhTk,6400
412
- stoobly_agent/config/mitmproxy.py,sha256=go7vzkSTr8Yz6k7P5tXgHB1agNu8uubHfaYa17LeR5c,2100
413
+ stoobly_agent/config/data_dir.py,sha256=lETERzeXIBYpjoEWRFRZ9NExO1RhdYT0XL4RDELhpP4,7484
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=rqWtvvV0eFJzxPOcG3aHz4AZk-DLa_Z4GkFonk7bsgw,5
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=WbIFRerTzgvHmiRg8OyI1WDuwFeMu1tHqsJJb46PDkc,2341
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.5.dist-info/LICENSE,sha256=8QKGyy45eN76Zk52h8gu1DKX2B_gbWgZ3nzDLofEbaE,548
711
- stoobly_agent-1.0.5.dist-info/METADATA,sha256=7Hn3-8EPo9N8ga1KnlMEpJGDs45MwJWS8NaJWe28rgE,3388
712
- stoobly_agent-1.0.5.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
713
- stoobly_agent-1.0.5.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
714
- stoobly_agent-1.0.5.dist-info/RECORD,,
713
+ stoobly_agent-1.0.7.dist-info/LICENSE,sha256=8QKGyy45eN76Zk52h8gu1DKX2B_gbWgZ3nzDLofEbaE,548
714
+ stoobly_agent-1.0.7.dist-info/METADATA,sha256=X94lZx1QYnVFktZwG09Z2Ca2a2vP8Gt65o_fscFAxKM,3388
715
+ stoobly_agent-1.0.7.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
716
+ stoobly_agent-1.0.7.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
717
+ stoobly_agent-1.0.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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)