63 lines
3.4 KiB
Python
63 lines
3.4 KiB
Python
"""User-authorized IM deployment transport. Credentials and relayed keys stay in memory."""
|
|
import sys,json,getpass,shlex,base64,hashlib
|
|
from pathlib import Path
|
|
sys.path.insert(0,str(Path(__file__).resolve().parent/'deploy-python'))
|
|
import paramiko
|
|
|
|
hosts=Path(__file__).resolve().parent/'im-deploy-known-hosts'
|
|
class PinFirstHost(paramiko.MissingHostKeyPolicy):
|
|
def missing_host_key(self,client,hostname,key):
|
|
print('SSH_HOST_KEY '+hostname+' SHA256:'+base64.b64encode(hashlib.sha256(key.asbytes()).digest()).decode().rstrip('='),flush=True)
|
|
client.get_host_keys().add(hostname,key.get_name(),key);client.save_host_keys(str(hosts))
|
|
client=paramiko.SSHClient()
|
|
if hosts.exists():client.load_host_keys(str(hosts))
|
|
client.set_missing_host_key_policy(PinFirstHost())
|
|
password=getpass.getpass('IM SSH password (memory only): ')
|
|
client.connect('192.168.110.111',username='ps',password=password,look_for_keys=False,allow_agent=False,timeout=12,auth_timeout=12)
|
|
group=None
|
|
def execute(c,command,timeout=45,sudo=False):
|
|
if sudo:command='sudo -S -p '+shlex.quote('')+' -- sh -c '+shlex.quote(command)
|
|
stdin,stdout,stderr=c.exec_command(command,timeout=timeout)
|
|
if sudo:stdin.write(password+'\n');stdin.flush()
|
|
stdin.channel.shutdown_write()
|
|
out=stdout.read();err=stderr.read();return stdout.channel.recv_exit_status(),out,err
|
|
print('IM_SSH_READY',flush=True)
|
|
for line in sys.stdin:
|
|
try:
|
|
request=json.loads(line);action=request['action']
|
|
if action=='close':break
|
|
if action in ('exec','exec_group'):
|
|
c=group if action=='exec_group' else client
|
|
status,out,err=execute(c,request['command'],request.get('timeout',45),request.get('sudo',False))
|
|
result={'exit':status,'out':out.decode('utf-8','replace'),'err':err.decode('utf-8','replace')}
|
|
elif action in ('upload','download','upload_group'):
|
|
c=group if action=='upload_group' else client
|
|
with c.open_sftp() as sftp:
|
|
if action in ('upload','upload_group'):sftp.put(request['local'],request['remote'])
|
|
else:sftp.get(request['remote'],request['local'])
|
|
result={'ok':True}
|
|
elif action=='connect_group':
|
|
group=paramiko.SSHClient();group.load_host_keys(str(Path(__file__).resolve().parent/'deploy-known-hosts'))
|
|
group.set_missing_host_key_policy(paramiko.RejectPolicy())
|
|
gp=getpass.getpass('Group SSH password (not stored): ')
|
|
group.connect('39.97.232.35',username='root',password=gp,look_for_keys=False,allow_agent=False,timeout=12,auth_timeout=12)
|
|
del gp;result={'group_connected':True}
|
|
elif action=='relay_key_to_group':
|
|
source=request['source'];target=request['target']
|
|
assert source.startswith('/opt/') or source.startswith('/www/')
|
|
assert target.startswith('/www/wwwroot/x.zhenyangtang.com.cn/shared/') and group is not None
|
|
status,key,err=execute(client,'cat -- '+shlex.quote(source),sudo=True)
|
|
assert status==0 and len(key)==64 and all(c in b'0123456789abcdefABCDEF' for c in key),'invalid key source'
|
|
with group.open_sftp() as sftp:
|
|
try:sftp.stat(target);raise RuntimeError('target key already exists')
|
|
except FileNotFoundError:pass
|
|
with sftp.file(target,'wb') as f:f.write(key)
|
|
sftp.chmod(target,0o600)
|
|
del key;result={'key_relayed':True}
|
|
else:raise ValueError('unsupported action')
|
|
print(json.dumps(result,ensure_ascii=False),flush=True)
|
|
except Exception as e:
|
|
print(json.dumps({'error':type(e).__name__,'message':str(e)},ensure_ascii=False),flush=True)
|
|
if group:group.close()
|
|
client.close();del password
|