1#!/usr/bin/env python3
2
3"""Helper functions for ssh connections.
4Should be rewritten to use a persistent connection object.
5Clients should cache the retrieved zip file.
6Not authentication is not implemented. Only hosts from .ssh/config
7are supported and keys need to be set up.
8Also doesn't work because everything comes in as a zero length file for some reason.
9"""
10
11import logging
12
13# import paramiko
14
15from chart.common.path import Path
16
17logger = logging.getLogger()
18
19SSH_CONFIG_FILE = Path('~/.ssh/config').expand()
20SSH_ID_FILE = Path('~/.ssh/id_rsa').expand()
21
22# Warning: this doesn't work. Files retrieved always have length 0. Use the weird function below
23# instead
24# def ssh_open(connection, path):
25# """Retrieve `path` from `connection`.
26# Connection may be either:
27
28# * a shortcut from ~/.ssh/config
29# * a hostname, with same user as currently logged on (?)
30# * a string like "user@host"
31<<<hidden due to potential security issue>>>
32
33# Keys will be used if available, otherwise if connected to a live terminal
34<<<hidden due to potential security issue>>>
35
36# logger.debug('ssh_open conn {c} path {p}'.format(c=connection, p=path))
37
38# client = paramiko.SSHClient()
39# client._policy = paramiko.WarningPolicy()
40# client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
41
42# ssh_config = paramiko.SSHConfig()
43# user_config_file = os.path.expanduser("~/.ssh/config")
44# if os.path.exists(user_config_file):
45# with open(user_config_file) as f:
46# ssh_config.parse(f)
47
48# # cfg = {'hostname': options['hostname'], 'username': options['username]}
49# config = {'hostname': connection}
50
51# user_config = ssh_config.lookup(config['hostname'])
52# for k in ('hostname', 'user', 'port'):
53# if k in user_config:
54# config[k] = user_config[k]
55
56# if 'proxycommand' in user_config:
57# config['sock'] = paramiko.ProxyCommand(user_config['proxycommand'])
58
59# if 'user' in config:
60# config['username'] = config['user']
61# del config['user']
62
63# logger.debug('connection {c}'.format(c=config))
64# client.connect(**config)
65# # sftp = paramiko.SFTPClient(client)
66# sftp = client.open_sftp()
67# # print(sftp.listdir(os.path.dirname(path)))
68# return sftp.open(path, 'r', 102400)
69
70
71class SSHClient:
72 """Wrap up some paramiko ugliness."""
73
74 def __init__(self, connection):
75 """`connection` must be an entry from .ssh/config.
76<<<hidden due to potential security issue>>>
77 Could be improved greatly.
78 Parsing constructs like ssh://chart@fviprs01 would be nice (?)
79 for now strip the leading ssh://
80 """
81 # we import here because on the TCE it takes 1-2s and this file gets pulled
82 # in by basically everything
83 import paramiko
84 ssh_config = paramiko.SSHConfig()
85 if SSH_CONFIG_FILE.exists():
86 with SSH_CONFIG_FILE.open() as f:
87 ssh_config.parse(f)
88
89 host_config = ssh_config.lookup(connection)
90 hostname = host_config['hostname']
91 user = host_config['user']
92 port = host_config.get('port', 22)
93 transport = paramiko.Transport((hostname, port))
94 key = paramiko.RSAKey.from_private_key_file(str(SSH_ID_FILE))
95 transport.start_client()
96 transport.auth_publickey(user, key)
97 self.sftp = paramiko.SFTPClient.from_transport(transport)
98
99
100def ssh_open(connection, path):
101 """Retrieve `path` through `connection`.
102 For speed we could cache connections.
103 If speed is an issue don't use this and create a single SSHClient and reuse the connection.
104 """
105 logger.debug('sftp open {p}'.format(p=path))
106 return SSHClient(connection).sftp.open(path)