Search This Blog

Wednesday, August 29, 2012

How to control and manage you cloud servers from a bastion server

The idea is very simple. We would like to have one (or more) server that belongs to our cloud account and use it only to execute and orchestrate various execution tasks. A diagram below is showing the concept. From the machine you have to ssh to your bastion (1) and then from it you can run any further tasks (2).



An hardened servers should be used as a bastion host. This server will provide the following functions:

  • Act as a secure gateway into the cloud environment 
  • You should configure all other server to accept connections from this server only
  • From bastion you can lunch tasks that will perform further actions on the other cloud servers

Problem

How to run ssh or scp command over ssh that is initiated by the client and need to be executed from a bastion host on other cloud server.

Solution

This relatively long script written in python that uses paramiko module demonstrates the idea. It can be definitely extended and improved but you get the idea I hope :).
#!/usr/bin/env python
import re
import time
import datetime
import paramiko
import traceback
DEBUG = 1
def log(message):
print("%s" % message)
def debug(message):
if DEBUG>0:
log("debug: %s" % message)
class Bastion:
def __init__(self, bastion_ip, bastion_pass ):
self.bastion_ip = bastion_ip
self.bastion_pass = bastion_pass
def run_scp_cmd(self, cloud_ip, passwd, file):
scp_opt=''
# if DEBUG:
# scp_opt="-v"
debug("[%s] trying scp a file from bastion %s to cloud server %s" % (cloud_ip, self.bastion_ip, cloud_ip ))
cmd='scp -q ' + scp_opt + ' -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=1 -o StrictHostKeyChecking=no %s root@%s:~/; echo scp status $? done.' % \
( file, cloud_ip )
debug("[%s] cmd=%s" % (cloud_ip ,cmd) )
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy( paramiko.AutoAddPolicy() )
ssh.connect(self.bastion_ip, username='root', password=self.bastion_pass)
chan = ssh.invoke_shell()
chan.settimeout(30)
# wait for the prompt
buff = ''
while not buff.endswith(':~# '):
resp = chan.recv(9999)
buff += resp
debug("[%s] %s" % (cloud_ip, resp) )
# Ssh and wait for the password prompt.
chan.send(cmd + '\n')
buff = ''
still_waiting_for_data=True
is_conn_lost=False
is_timeout=False
is_passwd=False
is_route=False
is_script_missing=False
while still_waiting_for_data :
resp = chan.recv(9999)
buff += resp
debug("[%s] %s" % (cloud_ip, resp) )
if buff.endswith('\'s password: ') :
is_passwd=True
elif buff.find('Connection timed out') > -1 :
is_timeout=True
elif buff.find('lost connection') > -1 :
is_conn_lost=True
elif buff.find('No route to host') > -1:
is_route=True
elif buff.find('No such file or directory') > -1:
is_script_missing=True
still_waiting_for_data= not ( is_timeout or is_conn_lost or is_passwd or is_route or is_script_missing )
debug( "[%s] is_timeout=%s, is_conn_lost=%s, is_passwd=%s is_route=%s is_script_missing=%s" % \
( cloud_ip, is_timeout, is_conn_lost, is_passwd, is_route, is_script_missing ) )
if is_script_missing :
log("ERROR, scp didn't succeed")
sys.exit(-1)
if is_passwd:
# Send the password and wait for a prompt.
time.sleep(3)
debug("[%s] sending pass for scp: <%s> " % (cloud_ip, passwd) )
chan.send(passwd + '\n')
buff = ''
while buff.find(' done.') < 0 :
resp = chan.recv(9999)
buff += resp
debug("[%s] %s" % (cloud_ip, resp) )
if buff.find('Connection timed out') > -1 or buff.find('lost connection') > -1 :
debug("[%s] scp aborted" % cloud_ip )
break
ret=re.search('scp status (\d+) done.', buff).group(1)
ssh.close()
debug("[%s] did scp execution succeeded: %s (%s)" % (cloud_ip, str(ret=='0'), ret ) )
if 0==int(ret):
return True
return False
def run_ssh_cmd(self, cloud_ip, passwd, command):
ssh_opt=""
cmd_opt=""
# if DEBUG:
# ssh_opt="-v"
# cmd_opt="debug"
debug("[%s] trying to execute a command on bastion host %s for the cloud server %s" % \
(cloud_ip, self.bastion_ip, cloud_ip ))
cmd='ssh -t %s -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=1 -o StrictHostKeyChecking=no root@%s "%s %s"; echo ssh status $? done.' % \
( ssh_opt , cloud_ip, command, cmd_opt )
debug("[%s] cmd=%s" % ( cloud_ip, cmd) )
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy( paramiko.AutoAddPolicy() )
ssh.connect(self.bastion_ip, username='root', password=self.bastion_pass)
chan = ssh.invoke_shell()
chan.settimeout(30)
buff = ''
while not buff.endswith(':~# '):
resp = chan.recv(9999)
buff += resp
debug("[%s] %s" % (cloud_ip, resp) )
# Ssh and wait for the password prompt.
chan.send(cmd + '\n')
buff = ''
still_waiting_for_data=True
is_conn_lost=False
is_timeout=False
is_passwd=False
is_route=False
while still_waiting_for_data :
resp = chan.recv(9999)
buff += resp
debug("[%s] %s" % (cloud_ip, resp) )
if buff.endswith('\'s password: ') :
is_passwd=True
elif buff.find('Connection timed out') > -1:
is_timeout=True
elif buff.find('lost connection') > -1:
is_conn_lost=True
elif buff.find('No route to host') > -1:
is_route=True
still_waiting_for_data= not ( is_timeout or is_conn_lost or is_passwd or is_route)
debug( "[%s] is_timeout=%s, is_conn_lost=%s, is_passwd=%s is_route=%s" % \
( cloud_ip, is_timeout, is_conn_lost, is_passwd, is_route ) )
if is_passwd:
# Send the password and wait for a prompt.
time.sleep(3)
debug("[%s] sending pass for ssh: <%s> " % (cloud_ip, passwd) )
chan.send(passwd + '\n')
buff = ''
while buff.find(' done.') < 0 :
resp = chan.recv(9999)
buff += resp
debug("[%s] %s" % (cloud_ip, resp) )
if buff.find('Connection timed out') > -1 or buff.find('lost connection') > -1 or \
buff.find('No route to host') > -1 :
debug("[%s] ssh aborted" % cloud_ip )
break
try:
ret=re.search( 'ssh status (\d+) done\.', buff).group(1)
except Exception, e:
log("ERROR, exception when parsing ssh output")
debug(traceback.format_exc())
ssh.close()
debug("[%s] ssh execution returned: %s " % (cloud_ip, ret) )
if int(ret)==0:
return True
else:
return False
if __name__ == "__main__":
b=Bastion('1.1.1.1', 'pass')
cloud_ip='10.177.111.111'
passwd='pass'
print("-----------------------------------------------")
file='/etc/issue'
if b.run_scp_cmd(cloud_ip, passwd, file):
log("scp ok")
print("-----------------------------------------------")
file='/etc/issue2'
if b.run_scp_cmd(cloud_ip, passwd, file):
log("scp ok")
print("-----------------------------------------------")
command='ls -la /root/'
if b.run_ssh_cmd(cloud_ip, passwd, command) :
log("ssh ok")
print("-----------------------------------------------")
command='hostname'
if b.run_ssh_cmd(cloud_ip, passwd, command) :
log("ssh ok")
view raw bastion.py hosted with ❤ by GitHub

No comments:

Post a Comment