merge Jumphost, modify license

This commit is contained in:
2023-12-06 10:53:42 -03:00
6 changed files with 200 additions and 49 deletions

View File

@ -1390,7 +1390,7 @@ Categorize the user's request based on the operation they want to perform on
def _createconfig(self, conf):
#Create config file
defaultconfig = {'config': {'case': False, 'idletime': 30, 'fzf': False}, 'connections': {}, 'profiles': { "default": { "host":"", "protocol":"ssh", "port":"", "user":"", "password":"", "options":"", "logs":"", "tags": "" }}}
defaultconfig = {'config': {'case': False, 'idletime': 30, 'fzf': False}, 'connections': {}, 'profiles': { "default": { "host":"", "protocol":"ssh", "port":"", "user":"", "password":"", "options":"", "logs":"", "tags": "", "jumphost":""}}}
if not os.path.exists(conf):
with open(conf, "w") as f:
json.dump(defaultconfig, f, indent = 4)
@ -1536,14 +1536,14 @@ Categorize the user's request based on the operation they want to perform on
return nodes
def _connections_add(self,*, id, host, folder='', subfolder='', options='', logs='', password='', port='', protocol='', user='', tags='', type = "connection" ):
def _connections_add(self,*, id, host, folder='', subfolder='', options='', logs='', password='', port='', protocol='', user='', tags='', jumphost='', type = "connection" ):
#Add connection from config
if folder == '':
self.connections[id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "tags": tags,"type": type}
self.connections[id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "tags": tags,"jumphost": jumphost,"type": type}
elif folder != '' and subfolder == '':
self.connections[folder][id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "tags": tags, "type": type}
self.connections[folder][id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "tags": tags, "jumphost": jumphost, "type": type}
elif folder != '' and subfolder != '':
self.connections[folder][subfolder][id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "tags": tags, "type": type}
self.connections[folder][subfolder][id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "tags": tags, "jumphost": jumphost, "type": type}
def _connections_del(self,*, id, folder='', subfolder=''):
@ -1572,9 +1572,9 @@ Categorize the user's request based on the operation they want to perform on
del self.connections[folder][subfolder]
def _profiles_add(self,*, id, host = '', options='', logs='', password='', port='', protocol='', user='', tags='' ):
def _profiles_add(self,*, id, host = '', options='', logs='', password='', port='', protocol='', user='', tags='', jumphost='' ):
#Add profile from config
self.profiles[id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "tags": tags}
self.profiles[id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "tags": tags, "jumphost": jumphost}
def _profiles_del(self,*, id ):
@ -2332,6 +2332,7 @@ Categorize the user's request based on the operation they want to perform on
newnode["options"] = newnodes["options"]
newnode["logs"] = newnodes["logs"]
newnode["tags"] = newnodes["tags"]
newnode["jumphost"] = newnodes["jumphost"]
newnode["user"] = newnodes["user"]
newnode["password"] = newnodes["password"]
count +=1
@ -2785,6 +2786,23 @@ Categorize the user's request based on the operation they want to perform on
raise inquirer.errors.ValidationError("", reason="Tags should be a python dictionary.".format(current))
return True
def _jumphost_validation(self, answers, current):
#Validation for Jumphost in inquirer when managing nodes
if current.startswith("@"):
if current[1:] not in self.profiles:
raise inquirer.errors.ValidationError("", reason="Profile {} don't exist".format(current))
elif current != "":
if current not in self.nodes :
raise inquirer.errors.ValidationError("", reason="Node {} don't exist.".format(current))
return True
def _profile_jumphost_validation(self, answers, current):
#Validation for Jumphost in inquirer when managing profiles
if current != "":
if current not in self.nodes :
raise inquirer.errors.ValidationError("", reason="Node {} don't exist.".format(current))
return True
def _default_validation(self, answers, current):
#Default validation type used in multiples questions in inquirer
if current.startswith("@"):
@ -2832,6 +2850,7 @@ Categorize the user's request based on the operation they want to perform on
questions.append(inquirer.Confirm("options", message="Edit Options?"))
questions.append(inquirer.Confirm("logs", message="Edit logging path/file?"))
questions.append(inquirer.Confirm("tags", message="Edit tags?"))
questions.append(inquirer.Confirm("jumphost", message="Edit jumphost?"))
questions.append(inquirer.Confirm("user", message="Edit User?"))
questions.append(inquirer.Confirm("password", message="Edit password?"))
answers = inquirer.prompt(questions)
@ -2843,11 +2862,13 @@ Categorize the user's request based on the operation they want to perform on
defaults = self.config.getitem(unique)
if "tags" not in defaults:
defaults["tags"] = ""
if "jumphost" not in defaults:
defaults["jumphost"] = ""
except:
defaults = { "host":"", "protocol":"", "port":"", "user":"", "options":"", "logs":"" , "tags":"", "password":""}
defaults = { "host":"", "protocol":"", "port":"", "user":"", "options":"", "logs":"" , "tags":"", "password":"", "jumphost":""}
node = {}
if edit == None:
edit = { "host":True, "protocol":True, "port":True, "user":True, "password": True,"options":True, "logs":True, "tags":True }
edit = { "host":True, "protocol":True, "port":True, "user":True, "password": True,"options":True, "logs":True, "tags":True, "jumphost":True }
questions = []
if edit["host"]:
questions.append(inquirer.Text("host", message="Add Hostname or IP", validate=self._host_validation, default=defaults["host"]))
@ -2873,6 +2894,10 @@ Categorize the user's request based on the operation they want to perform on
questions.append(inquirer.Text("tags", message="Add tags dictionary", validate=self._tags_validation, default=str(defaults["tags"]).replace("{","{{").replace("}","}}")))
else:
node["tags"] = defaults["tags"]
if edit["jumphost"]:
questions.append(inquirer.Text("jumphost", message="Add Jumphost node", validate=self._jumphost_validation, default=str(defaults["jumphost"]).replace("{","{{").replace("}","}}")))
else:
node["jumphost"] = defaults["jumphost"]
if edit["user"]:
questions.append(inquirer.Text("user", message="Pick username", validate=self._default_validation, default=defaults["user"]))
else:
@ -2911,11 +2936,13 @@ Categorize the user's request based on the operation they want to perform on
defaults = self.config.profiles[unique]
if "tags" not in defaults:
defaults["tags"] = ""
if "jumphost" not in defaults:
defaults["jumphost"] = ""
except:
defaults = { "host":"", "protocol":"", "port":"", "user":"", "options":"", "logs":"", "tags": "" }
defaults = { "host":"", "protocol":"", "port":"", "user":"", "options":"", "logs":"", "tags": "", "jumphost": ""}
profile = {}
if edit == None:
edit = { "host":True, "protocol":True, "port":True, "user":True, "password": True,"options":True, "logs":True, "tags":True }
edit = { "host":True, "protocol":True, "port":True, "user":True, "password": True,"options":True, "logs":True, "tags":True, "jumphost":True }
questions = []
if edit["host"]:
questions.append(inquirer.Text("host", message="Add Hostname or IP", default=defaults["host"]))
@ -2941,6 +2968,10 @@ Categorize the user's request based on the operation they want to perform on
questions.append(inquirer.Text("tags", message="Add tags dictionary", validate=self._profile_tags_validation, default=str(defaults["tags"]).replace("{","{{").replace("}","}}")))
else:
profile["tags"] = defaults["tags"]
if edit["jumphost"]:
questions.append(inquirer.Text("jumphost", message="Add Jumphost node", validate=self._profile_jumphost_validation, default=str(defaults["jumphost"]).replace("{","{{").replace("}","}}")))
else:
profile["jumphost"] = defaults["jumphost"]
if edit["user"]:
questions.append(inquirer.Text("user", message="Pick username", default=defaults["user"]))
else:
@ -2972,6 +3003,7 @@ Categorize the user's request based on the operation they want to perform on
questions.append(inquirer.Text("options", message="Pass extra options to protocol", validate=self._default_validation))
questions.append(inquirer.Text("logs", message="Pick logging path/file ", validate=self._default_validation))
questions.append(inquirer.Text("tags", message="Add tags dictionary", validate=self._tags_validation))
questions.append(inquirer.Text("jumphost", message="Add Jumphost node", validate=self._jumphost_validation))
questions.append(inquirer.Text("user", message="Pick username", validate=self._default_validation))
questions.append(inquirer.List("password", message="Password: Use a local password, no password or a list of profiles to reference?", choices=["Local Password", "Profiles", "No Password"]))
answer = inquirer.prompt(questions)
@ -2994,6 +3026,8 @@ Categorize the user's request based on the operation they want to perform on
return answer
def _type_node(self, arg_value, pat=re.compile(r"^[0-9a-zA-Z_.$@#-]+$")):
if arg_value == None:
raise ValueError("Missing argument node")
if not pat.match(arg_value):
raise ValueError(f"Argument error: {arg_value}")
return arg_value
@ -3326,7 +3360,7 @@ tasks:
</dd>
<dt id="connpy.node"><code class="flex name class">
<span>class <span class="ident">node</span></span>
<span>(</span><span>unique, host, options='', logs='', password='', port='', protocol='', user='', config='', tags='')</span>
<span>(</span><span>unique, host, options='', logs='', password='', port='', protocol='', user='', config='', tags='', jumphost='')</span>
</code></dt>
<dd>
<div class="desc"><p>This class generates a node object. Containts all the information and methods to connect and interact with a device using ssh or telnet.</p>
@ -3369,6 +3403,8 @@ tasks:
- tags (dict) : Tags useful for automation and personal porpuse
like "os", "prompt" and "screenleght_command"
- jumphost (str): Reference another node to be used as a jumphost
</code></pre></div>
<details class="source">
<summary>
@ -3391,7 +3427,7 @@ tasks:
&#39;&#39;&#39;
def __init__(self, unique, host, options=&#39;&#39;, logs=&#39;&#39;, password=&#39;&#39;, port=&#39;&#39;, protocol=&#39;&#39;, user=&#39;&#39;, config=&#39;&#39;, tags=&#39;&#39;):
def __init__(self, unique, host, options=&#39;&#39;, logs=&#39;&#39;, password=&#39;&#39;, port=&#39;&#39;, protocol=&#39;&#39;, user=&#39;&#39;, config=&#39;&#39;, tags=&#39;&#39;, jumphost=&#39;&#39;):
&#39;&#39;&#39;
### Parameters:
@ -3424,6 +3460,8 @@ tasks:
- tags (dict) : Tags useful for automation and personal porpuse
like &#34;os&#34;, &#34;prompt&#34; and &#34;screenleght_command&#34;
- jumphost (str): Reference another node to be used as a jumphost
&#39;&#39;&#39;
if config == &#39;&#39;:
self.idletime = 0
@ -3432,7 +3470,7 @@ tasks:
self.idletime = config.config[&#34;idletime&#34;]
self.key = config.key
self.unique = unique
attr = {&#34;host&#34;: host, &#34;logs&#34;: logs, &#34;options&#34;:options, &#34;port&#34;: port, &#34;protocol&#34;: protocol, &#34;user&#34;: user, &#34;tags&#34;: tags}
attr = {&#34;host&#34;: host, &#34;logs&#34;: logs, &#34;options&#34;:options, &#34;port&#34;: port, &#34;protocol&#34;: protocol, &#34;user&#34;: user, &#34;tags&#34;: tags, &#34;jumphost&#34;: jumphost}
for key in attr:
profile = re.search(&#34;^@(.*)&#34;, str(attr[key]))
if profile and config != &#39;&#39;:
@ -3455,6 +3493,45 @@ tasks:
self.password.append(config.profiles[profile.group(1)][&#34;password&#34;])
else:
self.password = [password]
if self.jumphost != &#34;&#34; and config != &#39;&#39;:
self.jumphost = config.getitem(self.jumphost)
for key in self.jumphost:
profile = re.search(&#34;^@(.*)&#34;, str(self.jumphost[key]))
if profile:
try:
self.jumphost[key] = config.profiles[profile.group(1)][key]
except:
self.jumphost[key] = &#34;&#34;
elif self.jumphost[key] == &#39;&#39; and key == &#34;protocol&#34;:
try:
self.jumphost[key] = config.profiles[&#34;default&#34;][key]
except:
self.jumphost[key] = &#34;ssh&#34;
if isinstance(self.jumphost[&#34;password&#34;],list):
jumphost_password = []
for i, s in enumerate(self.jumphost[&#34;password&#34;]):
profile = re.search(&#34;^@(.*)&#34;, self.jumphost[&#34;password&#34;][i])
if profile:
jumphost_password.append(config.profiles[profile.group(1)][&#34;password&#34;])
self.jumphost[&#34;password&#34;] = jumphost_password
else:
self.jumphost[&#34;password&#34;] = [self.jumphost[&#34;password&#34;]]
if self.jumphost[&#34;password&#34;] != [&#34;&#34;]:
self.password = self.jumphost[&#34;password&#34;] + self.password
if self.jumphost[&#34;protocol&#34;] == &#34;ssh&#34;:
jumphost_cmd = self.jumphost[&#34;protocol&#34;] + &#34; -W %h:%p&#34;
if self.jumphost[&#34;port&#34;] != &#39;&#39;:
jumphost_cmd = jumphost_cmd + &#34; -p &#34; + self.jumphost[&#34;port&#34;]
if self.jumphost[&#34;options&#34;] != &#39;&#39;:
jumphost_cmd = jumphost_cmd + &#34; &#34; + self.jumphost[&#34;options&#34;]
if self.jumphost[&#34;user&#34;] == &#39;&#39;:
jumphost_cmd = jumphost_cmd + &#34; {}&#34;.format(self.jumphost[&#34;host&#34;])
else:
jumphost_cmd = jumphost_cmd + &#34; {}&#34;.format(&#34;@&#34;.join([self.jumphost[&#34;user&#34;],self.jumphost[&#34;host&#34;]]))
self.jumphost = f&#34;-o ProxyCommand=\&#34;{jumphost_cmd}\&#34;&#34;
else:
self.jumphost = &#34;&#34;
def _passtx(self, passwords, *, keyfile=None):
# decrypts passwords, used by other methdos.
@ -3789,6 +3866,8 @@ tasks:
cmd = cmd + &#34; &#34; + self.options
if self.logs != &#39;&#39;:
self.logfile = self._logfile()
if self.jumphost != &#39;&#39;:
cmd = cmd + &#34; &#34; + self.jumphost
if self.password[0] != &#39;&#39;:
passwords = self._passtx(self.password)
else:
@ -3797,7 +3876,7 @@ tasks:
cmd = cmd + &#34; {}&#34;.format(self.host)
else:
cmd = cmd + &#34; {}&#34;.format(&#34;@&#34;.join([self.user,self.host]))
expects = [&#39;yes/no&#39;, &#39;refused&#39;, &#39;supported&#39;, &#39;Invalid|[u|U]sage:&#39;, &#39;ssh-keygen.*\&#34;&#39;, &#39;timeout|timed.out&#39;, &#39;unavailable&#39;, &#39;closed&#39;, &#39;[p|P]assword:|[u|U]sername:&#39;, r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;, &#39;suspend&#39;, pexpect.EOF, pexpect.TIMEOUT, &#34;No route to host&#34;, &#34;resolve hostname&#34;, &#34;no matching&#34;, &#34;bad permissions&#34;]
expects = [&#39;yes/no&#39;, &#39;refused&#39;, &#39;supported&#39;, &#39;Invalid|[u|U]sage: (ssh|sftp)&#39;, &#39;ssh-keygen.*\&#34;&#39;, &#39;timeout|timed.out&#39;, &#39;unavailable&#39;, &#39;closed&#39;, &#39;[p|P]assword:|[u|U]sername:&#39;, r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;, &#39;suspend&#39;, pexpect.EOF, pexpect.TIMEOUT, &#34;No route to host&#34;, &#34;resolve hostname&#34;, &#34;no matching&#34;, &#34;bad permissions&#34;]
elif self.protocol == &#34;telnet&#34;:
cmd = &#34;telnet &#34; + self.host
if self.port != &#39;&#39;:
@ -3810,7 +3889,7 @@ tasks:
passwords = self._passtx(self.password)
else:
passwords = []
expects = [&#39;[u|U]sername:&#39;, &#39;refused&#39;, &#39;supported&#39;, &#39;cipher&#39;, &#39;ssh-keygen.*\&#34;&#39;, &#39;timeout|timed.out&#39;, &#39;unavailable&#39;, &#39;closed&#39;, &#39;[p|P]assword:&#39;, r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;, &#39;suspend&#39;, pexpect.EOF, pexpect.TIMEOUT, &#34;No route to host&#34;, &#34;resolve hostname&#34;, &#34;no matching&#34;, &#34;bad permissions&#34;]
expects = [&#39;[u|U]sername:&#39;, &#39;refused&#39;, &#39;supported&#39;, &#39;invalid option&#39;, &#39;ssh-keygen.*\&#34;&#39;, &#39;timeout|timed.out&#39;, &#39;unavailable&#39;, &#39;closed&#39;, &#39;[p|P]assword:&#39;, r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;, &#39;suspend&#39;, pexpect.EOF, pexpect.TIMEOUT, &#34;No route to host&#34;, &#34;resolve hostname&#34;, &#34;no matching&#34;, &#34;bad permissions&#34;]
else:
raise ValueError(&#34;Invalid protocol: &#34; + self.protocol)
attempts = 1