├── .gitignore ├── .travis.yml ├── README.md ├── forward_socket └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | python_env/ 4 | *.egg-info/ 5 | *.pyc 6 | *.swp 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.3' 5 | - pypy 6 | script: 7 | - ./setup.py sdist 8 | - ./setup.py install 9 | - bash -c 'forward_socket; [[ $? -eq 2 ]]' 10 | deploy: 11 | provider: pypi 12 | user: thatpanda 13 | password: 14 | secure: Y8BEt2tEkXOuIVXJROQ6oV7WlnvU6JMGl5WWuQc+Rud2N9c+1oa+BotM64MtUouTLxRuxY23imexllYy7laYBtAs1NJljThz5bCGGjLoltaTzpq+AB+qo6bPoKEoEC+x3y4ihLcTIhk+sdGaxPwtJ8QWZR5zwTOB7PFUHAmF/AA= 15 | on: 16 | tags: true 17 | all_branches: true 18 | repo: RickyCook/ssh-forward-unix-socket 19 | condition: '"$TRAVIS_TAG" = "v$(python setup.py --version)"' 20 | python: 3.3 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssh-forward-unix-socket 2 | 3 | Forward a Unix socket over SSH 4 | 5 | ## Examples 6 | 7 | In all examples, `` represents a remote host. 8 | 9 | ### Forward a remote Docker socket 10 | ```bash 11 | sudo forward_socket \ 12 | --local_user $(id -un) \ 13 | "ssh -i $HOME/.ssh/id_rsa $(id -un)@" \ 14 | /var/run/docker.sock 15 | ``` 16 | - Sudo so that you can create the local `/var/run/docker.sock` 17 | - SSH into your host, with your current user's username, and your `id_rsa` (can't just use , because the sudo will ssh as `root@` and use root's keys) 18 | - Listen at `/var/run/docker.sock` (the same as the remote) 19 | - Set the listen socket's owner to your current user 20 | 21 | ### Forward a remote Docker socket, with connection pool 22 | ```bash 23 | sudo forward_socket 24 | --local_user $(id -un) \ 25 | "ssh -i $HOME/.ssh/id_rsa -o ControlMaster auto -o ControlPersist 600 -o ControlPath docker_ssh.sock $(id -un)@" \ 26 | /var/run/docker.sock 27 | ``` 28 | Mostly the same as the forwarding above, except: 29 | - `ControlMaster auto` adds connection pooling 30 | - `ControlPersist 600` sets the connection pool timeout to 600 31 | - `ControlPath docker_ssh.sock` creates the shared SSH socket `docker_ssh.sock` 32 | -------------------------------------------------------------------------------- /forward_socket: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import logging 5 | import os 6 | 7 | try: 8 | import shutil 9 | SOCAT_COMMAND = shutil.which('socat') 10 | except AttributeError: 11 | import distutils.spawn 12 | SOCAT_COMMAND = distutils.spawn.find_executable('socat') 13 | 14 | PARSER = argparse.ArgumentParser(description="Forward unix sockets over SSH") 15 | PARSER.add_argument("--local_path", help="override the local path. If not given, mirrors the remote_path") 16 | PARSER.add_argument("--local_user", help="try to set the user the local socket is owned by") 17 | PARSER.add_argument("--local_group", help="try to set the group the local socket is owned by") 18 | PARSER.add_argument("--local_mode", help="try to set the file mode of the local socket") 19 | PARSER.add_argument("ssh_command", help="command to connect to the remote host. Example: ssh myuser@myhost") 20 | PARSER.add_argument("remote_path", help="path to the remote socket to forward") 21 | 22 | 23 | logging.basicConfig(level=logging.DEBUG) 24 | 25 | def main(): 26 | args = PARSER.parse_args() 27 | logging.debug(args) 28 | 29 | socat_local_opts = [] 30 | for opt_name in ('user', 'group', 'mode'): 31 | opt_val = getattr(args, 'local_%s' % opt_name) 32 | if opt_val: 33 | socat_local_opts.append('%s=%s' % (opt_name, opt_val)) 34 | 35 | socat_local_opts_suffix = ''.join(( 36 | ',%s' % opt_string for opt_string in socat_local_opts 37 | )) 38 | 39 | real_socat_command = (SOCAT_COMMAND, 40 | 'UNIX-LISTEN:{local_path},reuseaddr,fork%s' % socat_local_opts_suffix, 41 | 'EXEC:{ssh_command} socat STDIO UNIX-CONNECT\\:{remote_path}', 42 | ) 43 | remote_path = args.remote_path 44 | local_path = args.local_path or remote_path 45 | real_socat_command = [ 46 | s.format( 47 | local_path=local_path, 48 | remote_path=remote_path, 49 | ssh_command=args.ssh_command, 50 | ) 51 | for s in real_socat_command] 52 | logging.debug("Real socat command: %s", real_socat_command) 53 | 54 | logging.info("Spawning the socat process") 55 | os.execvp(real_socat_command[0], real_socat_command) 56 | 57 | if __name__ == '__main__': 58 | main() 59 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | 4 | setup(name="ssh-forward-unix-socket", 5 | version="0.0.2", 6 | description="Forward a Unix socket over SSH", 7 | author="Ricky Cook", 8 | author_email="mail@thatpanda.com", 9 | scripts=['forward_socket'], 10 | ) 11 | --------------------------------------------------------------------------------