Skip to content

Commit 83a60e8

Browse files
committed
init
0 parents  commit 83a60e8

File tree

7 files changed

+188
-0
lines changed

7 files changed

+188
-0
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Proxy for docker remote context containers
2+
3+
This application bind and listen local ports and proxy connections to remote docker host with associated docker containers
4+
5+
6+
# Using
7+
8+
1. Install python 3
9+
2. Install requirements (`pip install -r requirements.txt`)
10+
3. Set docker host. Example: `export DOCKER_HOST="ssh://user@remote-docker"`
11+
4. Run: `python main.py`

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
docker
2+
paramiko
1.3 KB
Binary file not shown.
2.74 KB
Binary file not shown.

src/docker_service.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import docker
2+
import logging
3+
import os
4+
from urllib import parse
5+
6+
class DockerService(object):
7+
def __init__(self, docker_host = None):
8+
self.docker_host = docker_host
9+
self.docker_client = docker.DockerClient(base_url=docker_host, use_ssh_client=docker_host.startswith("ssh"))
10+
self.nodeInfo = self.docker_client.info()
11+
self.logger = logging.getLogger("DockerService")
12+
13+
def getRemoteHost(self):
14+
return parse.urlparse(os.getenv("DOCKER_HOST") if self.docker_host == None else self.docker_host).hostname
15+
16+
def getPublishedPorts(self):
17+
containers = self.docker_client.containers.list()
18+
ports = []
19+
for container in containers:
20+
for port in container.ports:
21+
if container.ports[port] == None:
22+
continue
23+
publishedPort = container.ports[port][0]["HostPort"]
24+
ports.append({"internal":port, "published":int(publishedPort)})
25+
return ports

src/main.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from docker_service import DockerService
2+
from proxy import ProxyType, ProxyServer
3+
import subprocess
4+
import json
5+
import logging
6+
import os
7+
import sys
8+
import time
9+
import argparse
10+
11+
DOCKER_HOST_ENV = os.getenv("DOCKER_HOST")
12+
ACTIVE_PROXIES = []
13+
# Configure Logging
14+
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(name)s - %(message)s', level=logging.DEBUG)
15+
16+
logging.getLogger("urllib3").setLevel(logging.INFO)
17+
logging.getLogger("docker").setLevel(logging.INFO)
18+
19+
def getDockerHostFromContext():
20+
current_context_name_cmd = subprocess.run(["docker", "context", "show"], capture_output=True)
21+
current_context_name = current_context_name_cmd.stdout.decode().replace("\n", "")
22+
current_context_cmd = subprocess.run(["docker", "context", "inspect", current_context_name], capture_output=True)
23+
current_context_json = json.loads(current_context_cmd.stdout.decode())
24+
return current_context_json[0]["Endpoints"]["docker"]["Host"]
25+
26+
def manageProxies(remote_docker_host_name, published_ports, listen_system_ports):
27+
global ACTIVE_PROXIES
28+
# Get only tcp services because only tcp proxy implemented
29+
published_ports = list(x["published"] for x in published_ports if x["internal"].endswith("tcp"))
30+
if not listen_system_ports:
31+
published_ports = list(x for x in published_ports if x > 1024)
32+
new_active_proxies = []
33+
# Add active proxy and stop inactive
34+
for proxy in ACTIVE_PROXIES:
35+
if(proxy.proxy_port in published_ports):
36+
new_active_proxies.append(proxy)
37+
published_ports.remove(proxy.proxy_port)
38+
else:
39+
proxy.stop()
40+
# Add and start new proxies
41+
for port in published_ports:
42+
proxy = ProxyServer(remote_docker_host_name, port, ProxyType.TCP)
43+
proxy.start()
44+
new_active_proxies.append(proxy)
45+
# Update proxy list
46+
ACTIVE_PROXIES = new_active_proxies
47+
48+
49+
50+
def mainLoop(docker_host, listen_system_ports):
51+
logging.info("Initialize docker remote proxy to `%s`" %docker_host)
52+
docker_service = DockerService(docker_host)
53+
remote_docker_host_name = docker_service.getRemoteHost()
54+
55+
try:
56+
while True:
57+
try:
58+
published_ports = docker_service.getPublishedPorts()
59+
manageProxies(remote_docker_host_name, published_ports, listen_system_ports)
60+
except Exception as e:
61+
logging.error(e)
62+
raise e
63+
time.sleep(60)
64+
except KeyboardInterrupt:
65+
logging.info('Docker remote proxy stopped!')
66+
67+
if __name__ == "__main__":
68+
logging.info('Starting docker remote proxy!')
69+
parser = argparse.ArgumentParser(description='Remote docker context proxy')
70+
parser.add_argument('--listen-system-ports', action=argparse.BooleanOptionalAction, default=False, help='Listen and Proxy system ports example: 22, 80, 443 etc.!')
71+
parser.add_argument('--host', type=str, default=None, help='Docker host uri')
72+
args = parser.parse_args()
73+
if DOCKER_HOST_ENV != None:
74+
mainLoop(DOCKER_HOST_ENV, args.listen_system_ports)
75+
elif args.host != None:
76+
mainLoop(args.host, args.listen_system_ports)
77+
else:
78+
mainLoop(getDockerHostFromContext(), args.listen_system_ports)

src/proxy.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from socketserver import BaseRequestHandler, TCPServer
2+
from socket import socket, AF_INET, SOCK_STREAM
3+
import threading
4+
import logging
5+
from enum import Enum, auto
6+
7+
8+
class ProxyType(Enum):
9+
TCP = auto()
10+
UDP = auto()
11+
12+
13+
class TcpProxySockHandler(BaseRequestHandler):
14+
"""
15+
Request Handler for the proxy server.
16+
Instantiated once time for each connection, and must
17+
override the handle() method for client communication.
18+
"""
19+
20+
PROXY_HOST = None
21+
PROXY_PORT = None
22+
Logger = logging.getLogger("TcpProxyHandler")
23+
24+
def handle(self):
25+
# self.request is the TCP socket connected to the client
26+
self.data = self.request.recv(1024)
27+
self.Logger.info("Passing data from: {}".format(self.client_address[0]))
28+
29+
# Create a socket to the localhost server
30+
sock = socket(AF_INET, SOCK_STREAM)
31+
# Try to connect to the server and send data
32+
try:
33+
sock.connect((self.PROXY_HOST, self.PROXY_PORT))
34+
sock.sendall(self.data)
35+
# Receive data from the server
36+
while 1:
37+
received = sock.recv(1024)
38+
if not received:
39+
break
40+
# Send back received data
41+
self.request.sendall(received)
42+
finally:
43+
sock.close()
44+
45+
46+
class ProxyServer():
47+
48+
def __init__(self, proxy_host, proxy_port, type=ProxyType.TCP):
49+
if(type != ProxyType.TCP):
50+
raise NotImplementedError("Only TCP Proxy implemented")
51+
self.logger = logging.getLogger("ProxyServer")
52+
self.proxy_host = proxy_host
53+
self.proxy_port = proxy_port
54+
self.type = type
55+
HOST = "127.0.0.1"
56+
57+
class ThreadedTcpProxyHandler(TcpProxySockHandler):
58+
PROXY_HOST = proxy_host
59+
PROXY_PORT = proxy_port
60+
61+
self.server = TCPServer((HOST, proxy_port), ThreadedTcpProxyHandler)
62+
self.ip, self.port = self.server.server_address
63+
self.logger.info("Creating %s proxy server with listen on %s and proxy to %s:%s", type, proxy_port, proxy_host, proxy_port)
64+
65+
def start(self):
66+
server_thread = threading.Thread(target=self.server.serve_forever)
67+
server_thread.start()
68+
self.logger.info("Starting %s proxy to %s:%s", self.type, self.proxy_host, self.proxy_port)
69+
70+
def stop(self):
71+
self.server.shutdown()
72+
self.logger.info("Stopping %s proxy to %s:%s", self.type, self.proxy_host, self.proxy_port)

0 commit comments

Comments
 (0)