-
-
Save kevinvalk/3ccd5b360fd568862b4a397a9df9ed26 to your computer and use it in GitHub Desktop.
| #!/usr/bin/python | |
| """ | |
| msysGit to Unix socket proxy | |
| ============================ | |
| This small script is intended to help use msysGit sockets with the new Windows Linux Subsystem (aka Bash for Windows). | |
| It was specifically designed to pass SSH keys from the KeeAgent module of KeePass secret management application to the | |
| ssh utility running in the WSL (it only works with Linux sockets). However, my guess is that it will have uses for other | |
| applications as well. | |
| In order to efficiently use it, I add it at the end of the ~/.bashrc file, like this: | |
| export SSH_AUTH_SOCK="/tmp/.ssh-auth-sock" | |
| ~/bin/msysgit2unix-socket.py /mnt/c/Users/User/keeagent.sock:$SSH_AUTH_SOCK | |
| Command line usage: msysgit2unix-socket.py [-h] [--downstream-buffer-size N] | |
| [--upstream-buffer-size N] [--listen-backlog N] | |
| [--timeout N] [--pidfile FILE] | |
| source:destination [source:destination ...] | |
| Positional arguments: | |
| source:destination A pair of a source msysGit and a destination Unix | |
| sockets. | |
| Optional arguments: | |
| -h, --help show this help message and exit | |
| --downstream-buffer-size N | |
| Maximum number of bytes to read at a time from the | |
| Unix socket. | |
| --upstream-buffer-size N | |
| Maximum number of bytes to read at a time from the | |
| msysGit socket. | |
| --listen-backlog N Maximum number of simultaneous connections to the Unix | |
| socket. | |
| --timeout N Timeout. | |
| --pidfile FILE Where to write the PID file. | |
| """ | |
| import argparse | |
| import asyncore | |
| import os | |
| import re | |
| import signal | |
| import socket | |
| import sys | |
| import errno | |
| import atexit | |
| # NOTE: Taken from http://stackoverflow.com/a/6940314 | |
| def PidExists(pid): | |
| """Check whether pid exists in the current process table. | |
| UNIX only. | |
| """ | |
| if pid < 0: | |
| return False | |
| if pid == 0: | |
| # According to "man 2 kill" PID 0 refers to every process | |
| # in the process group of the calling process. | |
| # On certain systems 0 is a valid PID but we have no way | |
| # to know that in a portable fashion. | |
| raise ValueError('invalid PID 0') | |
| try: | |
| os.kill(pid, 0) | |
| except OSError as err: | |
| if err.errno == errno.ESRCH: | |
| # ESRCH == No such process | |
| return False | |
| elif err.errno == errno.EPERM: | |
| # EPERM clearly means there's a process to deny access to | |
| return True | |
| else: | |
| # According to "man 2 kill" possible error values are | |
| # (EINVAL, EPERM, ESRCH) | |
| raise | |
| else: | |
| return True | |
| class UpstreamHandler(asyncore.dispatcher_with_send): | |
| """ | |
| This class handles the connection to the TCP socket listening on localhost that makes the msysGit socket. | |
| """ | |
| def __init__(self, downstream_dispatcher, upstream_path): | |
| asyncore.dispatcher.__init__(self) | |
| self.out_buffer = b'' | |
| self.downstream_dispatcher = downstream_dispatcher | |
| self.create_socket(socket.AF_INET, socket.SOCK_STREAM) | |
| self.connect((b'localhost', UpstreamHandler.load_tcp_port_from_msysgit_socket_file(upstream_path))) | |
| @staticmethod | |
| def load_tcp_port_from_msysgit_socket_file(path): | |
| with open(path, 'r') as f: | |
| m = re.search(b'>([0-9]+)', f.readline()) | |
| return int(m.group(1)) | |
| def handle_connect(self): | |
| pass | |
| def handle_close(self): | |
| self.close() | |
| self.downstream_dispatcher.close() | |
| def handle_read(self): | |
| data = self.recv(config.upstream_buffer_size) | |
| if data: | |
| self.downstream_dispatcher.send(data) | |
| class DownstreamHandler(asyncore.dispatcher_with_send): | |
| """ | |
| This class handles the connections that are being accepted on the Unix socket. | |
| """ | |
| def __init__(self, downstream_socket, upstream_path): | |
| asyncore.dispatcher.__init__(self, downstream_socket) | |
| self.out_buffer = b'' | |
| self.upstream_dispatcher = UpstreamHandler(self, upstream_path) | |
| def handle_close(self): | |
| self.close() | |
| self.upstream_dispatcher.close() | |
| def handle_read(self): | |
| data = self.recv(config.downstream_buffer_size) | |
| if data: | |
| self.upstream_dispatcher.send(data) | |
| class MSysGit2UnixSocketServer(asyncore.dispatcher): | |
| """ | |
| This is the "server" listening for connections on the Unix socket. | |
| """ | |
| def __init__(self, upstream_socket_path, unix_socket_path): | |
| asyncore.dispatcher.__init__(self) | |
| self.upstream_socket_path = upstream_socket_path | |
| self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) | |
| self.bind(unix_socket_path) | |
| self.listen(config.listen_backlog) | |
| def handle_accept(self): | |
| pair = self.accept() | |
| if pair is not None: | |
| sock, addr = pair | |
| DownstreamHandler(sock, self.upstream_socket_path) | |
| def build_config(): | |
| class ProxyAction(argparse.Action): | |
| def __call__(self, parser, namespace, values, option_string=None): | |
| proxies = [] | |
| for value in values: | |
| src_dst = value.partition(':') | |
| if src_dst[1] == b'': | |
| raise parser.error(b'Unable to parse sockets proxy pair "%s".' % value) | |
| proxies.append([src_dst[0], src_dst[2]]) | |
| setattr(namespace, self.dest, proxies) | |
| parser = argparse.ArgumentParser( | |
| description='Transforms msysGit compatible sockets to Unix sockets for the Windows Linux Subsystem.') | |
| parser.add_argument(b'--downstream-buffer-size', default=8192, type=int, metavar=b'N', | |
| help=b'Maximum number of bytes to read at a time from the Unix socket.') | |
| parser.add_argument(b'--upstream-buffer-size', default=8192, type=int, metavar=b'N', | |
| help=b'Maximum number of bytes to read at a time from the msysGit socket.') | |
| parser.add_argument(b'--listen-backlog', default=100, type=int, metavar=b'N', | |
| help=b'Maximum number of simultaneous connections to the Unix socket.') | |
| parser.add_argument(b'--timeout', default=60, type=int, help=b'Timeout.', metavar=b'N') | |
| parser.add_argument(b'--pidfile', default=b'/tmp/msysgit2unix-socket.pid', metavar=b'FILE', | |
| help=b'Where to write the PID file.') | |
| parser.add_argument(b'proxies', nargs=b'+', action=ProxyAction, metavar='source:destination', | |
| help=b'A pair of a source msysGit and a destination Unix sockets.') | |
| return parser.parse_args() | |
| def daemonize(): | |
| try: | |
| pid = os.fork() | |
| if pid > 0: | |
| sys.exit() | |
| except OSError: | |
| sys.stderr.write(b'Fork #1 failed.') | |
| sys.exit(1) | |
| os.chdir(b'/') | |
| os.setsid() | |
| os.umask(0) | |
| try: | |
| pid = os.fork() | |
| if pid > 0: | |
| sys.exit() | |
| except OSError: | |
| sys.stderr.write(b'Fork #2 failed.') | |
| sys.exit(1) | |
| sys.stdout.flush() | |
| sys.stderr.flush() | |
| si = open('/dev/null', 'r') | |
| so = open('/dev/null', 'a+') | |
| se = open('/dev/null', 'a+') | |
| os.dup2(si.fileno(), sys.stdin.fileno()) | |
| os.dup2(so.fileno(), sys.stdout.fileno()) | |
| os.dup2(se.fileno(), sys.stderr.fileno()) | |
| pid = str(os.getpid()) | |
| with open(config.pidfile, 'w+') as f: | |
| f.write(b'%s\n' % pid) | |
| def cleanup(): | |
| try: | |
| for pair in config.proxies: | |
| if os.path.exists(pair[1]): | |
| os.remove(pair[1]) | |
| if os.path.exists(config.pidfile): | |
| os.remove(config.pidfile) | |
| except Exception as e: | |
| sys.stderr.write(b'%s' % (e)) | |
| if __name__ == b'__main__': | |
| config = build_config() | |
| if os.path.exists(config.pidfile): | |
| # Check if process is really running, if not run cleanup | |
| f = open(config.pidfile, 'r') | |
| if PidExists(int(f.readline().strip())): | |
| sys.stderr.write(b'%s: Already running (or at least pidfile "%s" already exists).\n' % (sys.argv[0], config.pidfile)) | |
| sys.exit(0) | |
| else: | |
| cleanup() | |
| for pair in config.proxies: | |
| MSysGit2UnixSocketServer(pair[0], pair[1]) | |
| daemonize() | |
| # Redundant cleanup :) | |
| atexit.register(cleanup) | |
| signal.signal(signal.SIGINT, cleanup) | |
| signal.signal(signal.SIGTERM, cleanup) | |
| asyncore.loop(config.timeout, True) |
@duebbert, @kevenvalk: I get a notice each time I open an extra console:
/home/build/bin/msysgit2unix-socket.py: Already running (or at least pidfile "/tmp/msysgit2unix-socket.pid" already exists).
I think this notice may be helpful, but in this case (on startup) it is rather annoying.
@kevinvalk this should use either /usr/bin/python2 or /usr/bin/env python2
I tried to use this script in WSL2 and got an error when trying to read keys. I have WSL and WSL2 with the same configurations.
WSL
prart of output strace ssh-add -l
socket(AF_UNIX, SOCK_STREAM, 0) = 3
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
connect(3, {sa_family=AF_UNIX, sun_path="/tmp/.ssh-auth-sock"}, 110) = 0
write(3, "\0\0\0\1", 4) = 4
write(3, "\v", 1) = 1
read(3, "\0\0\6\241", 4) = 4
read(3, "\f\0\0\0\4\0\0\2\27\0\0\0\7ssh-rsa\0\0\0\3\1\0\1\0\0\2\1\0"..., 1024) = 1024
read(3, "\216\213\305HQ\226\241\177,O\374\315\333\33\302\273\341\272\2526\34\3\260\263g\327\17\330\25\17\360\36"..., 673) = 673
fstat(1, {st_mode=S_IFCHR|0660, st_rdev=makedev(4, 3), ...}) = 0
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
write(1, "4096 SHA256:ks4Ep+nc/QHwE0iWtVbE"..., 694096 SHA256:ks4Ep+nc/QHwE0iWtBbEexmECco2rO06W3dIWlndtgU id_rsa (RSA)
) = 69
write(1, "4096 SHA256:KBU39i4Ca+4AQo7yviJt"..., 734096 SHA256:KBU39i4Ca+4AQo7yvTJtlNm/Iyxv/BaqHAI7lP0z6js ..tfat1 (RSA)
) = 73
close(3) = 0
exit_group(0) = ?
+++ exited with 0 +++
WSL2
strace ssh-add -l
socket(AF_UNIX, SOCK_STREAM, 0) = 3
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
connect(3, {sa_family=AF_UNIX, sun_path="/tmp/.ssh-auth-sock"}, 110) = 0
write(3, "\0\0\0\1", 4) = 4
write(3, "\v", 1) = 1
read(3, "", 4) = 0
write(2, "error fetching identities: commu"..., 59error fetching identities: communication with agent failed
) = 59
close(3) = 0
exit_group(1) = ?
+++ exited with 1 +++
You might find this Ansible role to install and configure the script useful: https://github.com/ColOfAbRiX/role-wsl-keeagent
I also made few changes to the script (original script linked) https://github.com/ColOfAbRiX/role-wsl-keeagent/blob/master/files/msysgit2unix-socket.py and added a SystemV init script https://github.com/ColOfAbRiX/role-wsl-keeagent/blob/master/templates/wsl-keeagent.j2
Any guide on how to get WSL2 working with KeeAgent?
Update to use pthon3 ? Python2 no longer included in ubuntu
Update to use pthon3 ? Python2 no longer included in ubuntu
Please try this fork: https://gist.github.com/fktpp/ae4c93240890a1488c75e14f32ebc532
@FlorinAsavoaie @kevinvalk - I fixed problems with strings incorrectly marked as bytes. See my fork: https://gist.github.com/duebbert/4298b5f4eb7cc064b09e9d865dd490c9