Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.

Commit f325375

Browse files
dfanglalexrashed
authored andcommitted
first version of a different approach (tls proxy)
1 parent 792adf1 commit f325375

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

localstack/utils/server/proxy_server.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import logging
2+
import os
23
import select
34
import socket
5+
import ssl
6+
from concurrent.futures import ThreadPoolExecutor
47
from typing import Union
58

69
from localstack.constants import BIND_HOST, LOCALHOST_IP
10+
from localstack.utils.files import new_tmp_file, save_file
711
from localstack.utils.functions import run_safe
812
from localstack.utils.numbers import is_number
13+
from localstack.utils.serving import Server
14+
from localstack.utils.ssl import create_ssl_cert
915
from localstack.utils.threads import start_worker_thread
1016

1117
LOG = logging.getLogger(__name__)
@@ -73,3 +79,98 @@ def handle_request(s_src, thread):
7379
start_worker_thread(lambda *args, _thread: handle_request(src_socket, _thread))
7480
except socket.timeout:
7581
pass
82+
83+
84+
def _save_cert_keys(client_cert_key: tuple[str, str]) -> tuple[str, str]:
85+
"""
86+
Save the given cert / key into files and returns their filename
87+
:param client_cert_key: tuple with (client_cert, client_key)
88+
:return: tuple of paths to files containing (client_cert, client_key)
89+
"""
90+
cert_file = client_cert_key[0]
91+
if not os.path.exists(cert_file):
92+
cert_file = new_tmp_file()
93+
save_file(cert_file, client_cert_key[0])
94+
key_file = client_cert_key[1]
95+
if not os.path.exists(key_file):
96+
key_file = new_tmp_file()
97+
save_file(key_file, client_cert_key[1])
98+
return cert_file, key_file
99+
100+
101+
class TLSProxyServer(Server):
102+
thread_pool: ThreadPoolExecutor
103+
client_certs: tuple[str, str]
104+
socket: socket.socket | None
105+
target_host: str
106+
target_port: str
107+
108+
def __init__(
109+
self,
110+
port: int,
111+
target: str,
112+
host: str = "localhost",
113+
client_certs: tuple[str, str] | None = None,
114+
):
115+
super().__init__(port, host)
116+
self.target_host, _, self.target_port = target.partition(":")
117+
self.thread_pool = ThreadPoolExecutor()
118+
self.client_certs = client_certs
119+
self.socket = None
120+
121+
def _handle_socket(self, source_socket: ssl.SSLSocket, client_address: str) -> None:
122+
LOG.debug(
123+
"Handling connection from %s to %s:%s",
124+
client_address,
125+
self.target_host,
126+
self.target_port,
127+
)
128+
try:
129+
context = ssl.create_default_context()
130+
context.check_hostname = False
131+
context.verify_mode = ssl.CERT_NONE
132+
if self.client_certs:
133+
LOG.debug("Configuring ssl proxy to use client certs")
134+
cert_file, key_file = _save_cert_keys(client_cert_key=self.client_certs)
135+
context.load_cert_chain(certfile=cert_file, keyfile=key_file)
136+
with socket.create_connection((self.target_host, int(self.target_port))) as sock:
137+
with context.wrap_socket(sock, server_hostname=self.target_host) as target_socket:
138+
sockets = [source_socket, target_socket]
139+
while not self._stopped.is_set():
140+
s_read, _, _ = select.select(sockets, [], [])
141+
142+
for s in s_read:
143+
data = s.recv(BUFFER_SIZE)
144+
if not data:
145+
return
146+
147+
if s == source_socket:
148+
target_socket.sendall(data)
149+
elif s == target_socket:
150+
source_socket.sendall(data)
151+
except Exception as e:
152+
LOG.warning(
153+
"Error while proxying SSL request: %s", e, exc_info=LOG.isEnabledFor(logging.DEBUG)
154+
)
155+
156+
def do_run(self):
157+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
158+
159+
_, cert_file_name, key_file_name = create_ssl_cert()
160+
context.load_cert_chain(cert_file_name, key_file_name)
161+
162+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
163+
self.socket = sock
164+
sock.bind((self.host, self.port))
165+
sock.listen()
166+
with context.wrap_socket(sock, server_side=True) as ssock:
167+
while not self._stopped.is_set():
168+
try:
169+
conn, addr = ssock.accept()
170+
self.thread_pool.submit(self._handle_socket, conn, addr)
171+
except Exception as e:
172+
LOG.exception("Error accepting socket: %s", e)
173+
174+
def do_shutdown(self):
175+
if self.socket:
176+
self.socket.close()

0 commit comments

Comments
 (0)