OpenSSH is an awesome implementation of the ssh protocol and I use it a lot. However, to connect to hosts at work I sometimes need to traverse many firewalls, using insane ssh configurations such as:
Host foo ProxyCommand ssh -A host1 ssh -A host2 ssh -A host3 nc %h %p
Which means that connecting to host foo takes a lot of time. Using ControlMaster connections around helps, but then you have to remember to close the shell which is the ControlMaster last (~& helps though, but that’s cheating).
After perusing the ssh(1) and ssh_config(5) manpages (and parts of the OpenSSH source), I whipped up a relatively simple python script that takes away this limitation and also leaves the ControlMaster connection around for future re-use. It also runs ssh-add for you if your agent has no key yet.
It saves me quite a bit of hassle on a daily basis so it could be useful for you as well if you depend on ssh a lot.
You’ll want this in your ~/.ssh/config to enjoy it most:
ForwardAgent yes ControlMaster auto ControlPath ~/.ssh/ssh_control_%h_%p_%r
The script (download):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | #!/usr/bin/python # # Simple wrapper around SSH to achieve the following: # - Add keys to a running ssh-agent if no keys have been added yet # - Set up a controlmaster connection the first time you connect to a host # - my_ssh -Q kills all controlmaster connections # Works best with 'alias ssh=my_ssh' in your ~/.bashrc (or the equivalent for # your shell) # # (c)2007 Dennis Kaarsemaker <dennis@kaarsemaker.net> # # Log: # 2007/06/20 # - Initial version # 2007/06/21 # - Use paramiko if possible # 2007/09/18 # - Don't use a ControlMaster if an explicit forwarding is requested # - Guard against KeyboardInterrupt whilst running subprocesses # - Move argument error handling up # 2007/09/22 # - Really don't use controlmaster when -L / -R is used import os, sys, subprocess, getopt # Check whether the keys are added to the agent. Prefer paramiko, but fall back # to opening a pipe to ssh-add -l try: import paramiko.agent a = paramiko.agent.Agent() if not a.keys: try: ret = subprocess.Popen(c_args).wait() if ret: sys.exit(ret) except KeyboardInterrupt: sys.exit(130) a.close() except ImportError: if 'identities' in subprocess.Popen(['/usr/bin/ssh-add','-l'], stdout=subprocess.PIPE).communicate()[0]: try: ret = subprocess.Popen(c_args).wait() if ret: sys.exit(ret) except KeyboardInterrupt: sys.exit(130) # Parse arguments c_args = ['/usr/bin/ssh','-f','-N','-n'] try: (opts, args) = getopt.getopt(sys.argv[1:], 'Q1246AaCcfgKkMNnqsTtVvXxYb:c:D::F:i:L:l:m:O:o:p:R:S:w') except getopt.GetoptError: # Let SSH do the error reporting sys.argv[0] = '/usr/bin/ssh' os.execve('/usr/bin/ssh', sys.argv, os.environ) if ('-Q','') in opts: import glob, struct, socket # Kill all existing ssh controlmasters fd = open(os.path.expanduser('~/.ssh/config')) for line in fd: line = line.strip() if line.startswith('ControlPath'): path = line.split()[1].replace('%h','*').replace('%p','*').replace('%h','*').replace('%r','*') sockets = glob.glob(os.path.expanduser(path)) msg = struct.pack('>IBII',9,1,3,0) # Read the source luke :) (payload length, type, exit command, flags) for sock in sockets: try: s = socket.socket(socket.AF_UNIX) s.connect(sock) s.send(msg) s.recv() except: pass sys.exit(0) for a in 'ADKLRXY': if '-' + a in [x[0] for x in opts]: # Don't use a controlmaster when requesting any forwarding explicitely sys.argv.insert(1, '-oControlPath=none') break else: if len(args) and ('-O' not in [x[0] for x in opts]): host = args[0] c_args += sys.argv[1:sys.argv.index(host)+1] # Start the control connection if needed fd = open('/dev/null','w') if subprocess.Popen(['/usr/bin/ssh','-O','check',host], stdout=fd, stderr=fd).wait(): try: ret = subprocess.Popen(c_args).wait() if ret: sys.exit(ret) except KeyboardInterrupt: sys.exit(130) fd.close() # Don't use subprocess here -- we want to replace ourselves sys.argv[0] = '/usr/bin/ssh' os.execve('/usr/bin/ssh', sys.argv, os.environ) |