Add features:
- New protocols: Docker and Kubectl - Add contexts to filter the number of nodes - Add option to modify the api using plugins - Minor bug fixes
This commit is contained in:
@ -23,30 +23,33 @@
|
||||
</header>
|
||||
<section id="section-intro">
|
||||
<h2 id="connection-manager">Connection manager</h2>
|
||||
<p>Connpy is a connection manager that allows you to store nodes to connect them fast and password free.</p>
|
||||
<p>Connpy is a SSH, SFTP, Telnet, kubectl, and Docker pod connection manager and automation module for Linux, Mac, and Docker.</p>
|
||||
<h3 id="features">Features</h3>
|
||||
<pre><code>- You can generate profiles and reference them from nodes using @profilename so you dont
|
||||
need to edit multiple nodes when changing password or other information.
|
||||
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. Then can
|
||||
be referenced using node@subfolder@folder or node@folder
|
||||
- If you have too many nodes. Get completion script using: conn config --completion.
|
||||
Or use fzf installing pyfzf and running conn config --fzf true
|
||||
- Create in bulk, copy, move, export and import nodes for easy management.
|
||||
- Run automation scripts in network devices.
|
||||
- use GPT AI to help you manage your devices.
|
||||
<pre><code>- Manage connections using SSH, SFTP, Telnet, kubectl, and Docker exec.
|
||||
- Set contexts to manage specific nodes from specific contexts (work/home/clients/etc).
|
||||
- You can generate profiles and reference them from nodes using @profilename so you don't
|
||||
need to edit multiple nodes when changing passwords or other information.
|
||||
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. They can
|
||||
be referenced using node@subfolder@folder or node@folder.
|
||||
- If you have too many nodes, get a completion script using: conn config --completion.
|
||||
Or use fzf by installing pyfzf and running conn config --fzf true.
|
||||
- Create in bulk, copy, move, export, and import nodes for easy management.
|
||||
- Run automation scripts on network devices.
|
||||
- Use GPT AI to help you manage your devices.
|
||||
- Add plugins with your own scripts.
|
||||
- Much more!
|
||||
</code></pre>
|
||||
<h3 id="usage">Usage</h3>
|
||||
<pre><code>usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
||||
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config} ...
|
||||
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config,sync,context} ...
|
||||
|
||||
positional arguments:
|
||||
node|folder node[@subfolder][@folder]
|
||||
Connect to specific node or show all matching nodes
|
||||
[@subfolder][@folder]
|
||||
Show all available connections globaly or in specified path
|
||||
Options:
|
||||
node|folder node[@subfolder][@folder]
|
||||
Connect to specific node or show all matching nodes
|
||||
[@subfolder][@folder]
|
||||
Show all available connections globally or in specified path
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-v, --version Show version
|
||||
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
||||
@ -70,6 +73,7 @@ Commands:
|
||||
plugin Manage plugins
|
||||
config Manage app config
|
||||
sync Sync config with Google
|
||||
context Manage contexts with regex matching
|
||||
</code></pre>
|
||||
<h3 id="manage-profiles">Manage profiles</h3>
|
||||
<pre><code>usage: conn profile [-h] (--add | --del | --mod | --show) profile
|
||||
@ -86,14 +90,26 @@ options:
|
||||
|
||||
</code></pre>
|
||||
<h3 id="examples">Examples</h3>
|
||||
<pre><code> conn profile --add office-user
|
||||
<pre><code> #Add new profile
|
||||
conn profile --add office-user
|
||||
#Add new folder
|
||||
conn --add @office
|
||||
#Add new subfolder
|
||||
conn --add @datacenter@office
|
||||
#Add node to subfolder
|
||||
conn --add server@datacenter@office
|
||||
#Add node to folder
|
||||
conn --add pc@office
|
||||
#Show node information
|
||||
conn --show server@datacenter@office
|
||||
#Connect to nodes
|
||||
conn pc@office
|
||||
conn server
|
||||
#Create and set new context
|
||||
conn context -a office .*@office
|
||||
conn context --set office
|
||||
#Run a command in a node
|
||||
conn run server ls -la
|
||||
</code></pre>
|
||||
<h2 id="plugin-requirements-for-connpy">Plugin Requirements for Connpy</h2>
|
||||
<h3 id="general-structure">General Structure</h3>
|
||||
@ -416,32 +432,35 @@ print(result)
|
||||
'''
|
||||
## Connection manager
|
||||
|
||||
Connpy is a connection manager that allows you to store nodes to connect them fast and password free.
|
||||
Connpy is a SSH, SFTP, Telnet, kubectl, and Docker pod connection manager and automation module for Linux, Mac, and Docker.
|
||||
|
||||
### Features
|
||||
- You can generate profiles and reference them from nodes using @profilename so you dont
|
||||
need to edit multiple nodes when changing password or other information.
|
||||
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. Then can
|
||||
be referenced using node@subfolder@folder or node@folder
|
||||
- If you have too many nodes. Get completion script using: conn config --completion.
|
||||
Or use fzf installing pyfzf and running conn config --fzf true
|
||||
- Create in bulk, copy, move, export and import nodes for easy management.
|
||||
- Run automation scripts in network devices.
|
||||
- use GPT AI to help you manage your devices.
|
||||
- Manage connections using SSH, SFTP, Telnet, kubectl, and Docker exec.
|
||||
- Set contexts to manage specific nodes from specific contexts (work/home/clients/etc).
|
||||
- You can generate profiles and reference them from nodes using @profilename so you don't
|
||||
need to edit multiple nodes when changing passwords or other information.
|
||||
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. They can
|
||||
be referenced using node@subfolder@folder or node@folder.
|
||||
- If you have too many nodes, get a completion script using: conn config --completion.
|
||||
Or use fzf by installing pyfzf and running conn config --fzf true.
|
||||
- Create in bulk, copy, move, export, and import nodes for easy management.
|
||||
- Run automation scripts on network devices.
|
||||
- Use GPT AI to help you manage your devices.
|
||||
- Add plugins with your own scripts.
|
||||
- Much more!
|
||||
|
||||
### Usage
|
||||
```
|
||||
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
||||
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config} ...
|
||||
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config,sync,context} ...
|
||||
|
||||
positional arguments:
|
||||
node|folder node[@subfolder][@folder]
|
||||
Connect to specific node or show all matching nodes
|
||||
[@subfolder][@folder]
|
||||
Show all available connections globaly or in specified path
|
||||
Options:
|
||||
node|folder node[@subfolder][@folder]
|
||||
Connect to specific node or show all matching nodes
|
||||
[@subfolder][@folder]
|
||||
Show all available connections globally or in specified path
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-v, --version Show version
|
||||
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
||||
@ -465,6 +484,7 @@ Commands:
|
||||
plugin Manage plugins
|
||||
config Manage app config
|
||||
sync Sync config with Google
|
||||
context Manage contexts with regex matching
|
||||
```
|
||||
|
||||
### Manage profiles
|
||||
@ -485,14 +505,26 @@ options:
|
||||
|
||||
### Examples
|
||||
```
|
||||
#Add new profile
|
||||
conn profile --add office-user
|
||||
#Add new folder
|
||||
conn --add @office
|
||||
#Add new subfolder
|
||||
conn --add @datacenter@office
|
||||
#Add node to subfolder
|
||||
conn --add server@datacenter@office
|
||||
#Add node to folder
|
||||
conn --add pc@office
|
||||
#Show node information
|
||||
conn --show server@datacenter@office
|
||||
#Connect to nodes
|
||||
conn pc@office
|
||||
conn server
|
||||
#Create and set new context
|
||||
conn context -a office .*@office
|
||||
conn context --set office
|
||||
#Run a command in a node
|
||||
conn run server ls -la
|
||||
```
|
||||
## Plugin Requirements for Connpy
|
||||
### General Structure
|
||||
@ -2497,7 +2529,7 @@ def getitems(self, uniques):
|
||||
- port (str): Port to connect to node, default 22 for ssh and 23
|
||||
for telnet.
|
||||
|
||||
- protocol (str): Select ssh or telnet. Default is ssh.
|
||||
- protocol (str): Select ssh, telnet, kubectl or docker. Default is ssh.
|
||||
|
||||
- user (str): Username to of the node.
|
||||
|
||||
@ -2555,7 +2587,7 @@ class node:
|
||||
- port (str): Port to connect to node, default 22 for ssh and 23
|
||||
for telnet.
|
||||
|
||||
- protocol (str): Select ssh or telnet. Default is ssh.
|
||||
- protocol (str): Select ssh, telnet, kubectl or docker. Default is ssh.
|
||||
|
||||
- user (str): Username to of the node.
|
||||
|
||||
@ -2824,6 +2856,14 @@ class node:
|
||||
connect = self._connect(timeout = timeout)
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
||||
if connect == True:
|
||||
# Attempt to set the terminal size
|
||||
try:
|
||||
self.child.setwinsize(65535, 65535)
|
||||
except Exception:
|
||||
try:
|
||||
self.child.setwinsize(10000, 10000)
|
||||
except Exception:
|
||||
pass
|
||||
if "prompt" in self.tags:
|
||||
prompt = self.tags["prompt"]
|
||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||
@ -2911,6 +2951,14 @@ class node:
|
||||
'''
|
||||
connect = self._connect(timeout = timeout)
|
||||
if connect == True:
|
||||
# Attempt to set the terminal size
|
||||
try:
|
||||
self.child.setwinsize(65535, 65535)
|
||||
except Exception:
|
||||
try:
|
||||
self.child.setwinsize(10000, 10000)
|
||||
except Exception:
|
||||
pass
|
||||
if "prompt" in self.tags:
|
||||
prompt = self.tags["prompt"]
|
||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||
@ -2966,47 +3014,101 @@ class node:
|
||||
return connect
|
||||
|
||||
@MethodHook
|
||||
def _connect(self, debug = False, timeout = 10, max_attempts = 3):
|
||||
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
|
||||
def _generate_ssh_sftp_cmd(self):
|
||||
cmd = self.protocol
|
||||
if self.idletime > 0:
|
||||
cmd += " -o ServerAliveInterval=" + str(self.idletime)
|
||||
if self.port:
|
||||
if self.protocol == "ssh":
|
||||
cmd += " -p " + self.port
|
||||
elif self.protocol == "sftp":
|
||||
cmd += " -P " + self.port
|
||||
if self.options:
|
||||
cmd += " " + self.options
|
||||
if self.jumphost:
|
||||
cmd += " " + self.jumphost
|
||||
user_host = f"{self.user}@{self.host}" if self.user else self.host
|
||||
cmd += f" {user_host}"
|
||||
return cmd
|
||||
|
||||
@MethodHook
|
||||
def _generate_telnet_cmd(self):
|
||||
cmd = f"telnet {self.host}"
|
||||
if self.port:
|
||||
cmd += f" {self.port}"
|
||||
if self.options:
|
||||
cmd += f" {self.options}"
|
||||
return cmd
|
||||
|
||||
@MethodHook
|
||||
def _generate_kube_cmd(self):
|
||||
cmd = f"kubectl exec {self.options} {self.host} -it --"
|
||||
kube_command = self.tags.get("kube_command", "/bin/bash") if isinstance(self.tags, dict) else "/bin/bash"
|
||||
cmd += f" {kube_command}"
|
||||
return cmd
|
||||
|
||||
@MethodHook
|
||||
def _generate_docker_cmd(self):
|
||||
cmd = f"docker {self.options} exec -it {self.host}"
|
||||
docker_command = self.tags.get("docker_command", "/bin/bash") if isinstance(self.tags, dict) else "/bin/bash"
|
||||
cmd += f" {docker_command}"
|
||||
return cmd
|
||||
|
||||
@MethodHook
|
||||
def _get_cmd(self):
|
||||
if self.protocol in ["ssh", "sftp"]:
|
||||
cmd = self.protocol
|
||||
if self.idletime > 0:
|
||||
cmd = cmd + " -o ServerAliveInterval=" + str(self.idletime)
|
||||
if self.port != '':
|
||||
if self.protocol == "ssh":
|
||||
cmd = cmd + " -p " + self.port
|
||||
elif self.protocol == "sftp":
|
||||
cmd = cmd + " -P " + self.port
|
||||
if self.options != '':
|
||||
cmd = cmd + " " + self.options
|
||||
if self.logs != '':
|
||||
self.logfile = self._logfile()
|
||||
if self.jumphost != '':
|
||||
cmd = cmd + " " + self.jumphost
|
||||
if self.password[0] != '':
|
||||
passwords = self._passtx(self.password)
|
||||
else:
|
||||
passwords = []
|
||||
if self.user == '':
|
||||
cmd = cmd + " {}".format(self.host)
|
||||
else:
|
||||
cmd = cmd + " {}".format("@".join([self.user,self.host]))
|
||||
expects = ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: (ssh|sftp)', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', '[p|P]assword:|[u|U]sername:', r'>$|#$|\$$|>.$|#.$|\$.$', 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"]
|
||||
return self._generate_ssh_sftp_cmd()
|
||||
elif self.protocol == "telnet":
|
||||
cmd = "telnet " + self.host
|
||||
if self.port != '':
|
||||
cmd = cmd + " " + self.port
|
||||
if self.options != '':
|
||||
cmd = cmd + " " + self.options
|
||||
if self.logs != '':
|
||||
self.logfile = self._logfile()
|
||||
if self.password[0] != '':
|
||||
passwords = self._passtx(self.password)
|
||||
else:
|
||||
passwords = []
|
||||
expects = ['[u|U]sername:', 'refused', 'supported', 'invalid option', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', '[p|P]assword:', r'>$|#$|\$$|>.$|#.$|\$.$', 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"]
|
||||
return self._generate_telnet_cmd()
|
||||
elif self.protocol == "kubectl":
|
||||
return self._generate_kube_cmd()
|
||||
elif self.protocol == "docker":
|
||||
return self._generate_docker_cmd()
|
||||
else:
|
||||
raise ValueError("Invalid protocol: " + self.protocol)
|
||||
raise ValueError(f"Invalid protocol: {self.protocol}")
|
||||
|
||||
@MethodHook
|
||||
def _connect(self, debug=False, timeout=10, max_attempts=3):
|
||||
cmd = self._get_cmd()
|
||||
passwords = self._passtx(self.password) if self.password[0] else []
|
||||
if self.logs != '':
|
||||
self.logfile = self._logfile()
|
||||
default_prompt = r'>$|#$|\$$|>.$|#.$|\$.$'
|
||||
prompt = self.tags.get("prompt", default_prompt) if isinstance(self.tags, dict) else default_prompt
|
||||
password_prompt = '[p|P]assword:|[u|U]sername:' if self.protocol != 'telnet' else '[p|P]assword:'
|
||||
|
||||
expects = {
|
||||
"ssh": ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: ssh', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
||||
"sftp": ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: sftp', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
||||
"telnet": ['[u|U]sername:', 'refused', 'supported', 'invalid|unrecognized option', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
||||
"kubectl": ['[u|U]sername:', '[r|R]efused', '[E|e]rror', 'DEPRECATED', pexpect.TIMEOUT, password_prompt, prompt, pexpect.EOF, "expired|invalid"],
|
||||
"docker": ['[u|U]sername:', 'Cannot', '[E|e]rror', 'failed', 'not a docker command', 'unknown', 'unable to resolve', pexpect.TIMEOUT, password_prompt, prompt, pexpect.EOF]
|
||||
}
|
||||
|
||||
error_indices = {
|
||||
"ssh": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
||||
"sftp": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
||||
"telnet": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
||||
"kubectl": [1, 2, 3, 4, 8], # Define error indices for kube
|
||||
"docker": [1, 2, 3, 4, 5, 6, 7] # Define error indices for docker
|
||||
}
|
||||
|
||||
eof_indices = {
|
||||
"ssh": [8, 9, 10, 11],
|
||||
"sftp": [8, 9, 10, 11],
|
||||
"telnet": [8, 9, 10, 11],
|
||||
"kubectl": [5, 6, 7], # Define eof indices for kube
|
||||
"docker": [8, 9, 10] # Define eof indices for docker
|
||||
}
|
||||
|
||||
initial_indices = {
|
||||
"ssh": [0],
|
||||
"sftp": [0],
|
||||
"telnet": [0],
|
||||
"kubectl": [0], # Define special indices for kube
|
||||
"docker": [0] # Define special indices for docker
|
||||
}
|
||||
|
||||
attempts = 1
|
||||
while attempts <= max_attempts:
|
||||
child = pexpect.spawn(cmd)
|
||||
@ -3014,54 +3116,55 @@ class node:
|
||||
print(cmd)
|
||||
self.mylog = io.BytesIO()
|
||||
child.logfile_read = self.mylog
|
||||
if len(passwords) > 0:
|
||||
loops = len(passwords)
|
||||
else:
|
||||
loops = 1
|
||||
|
||||
endloop = False
|
||||
for i in range(0, loops):
|
||||
for i in range(len(passwords) if passwords else 1):
|
||||
while True:
|
||||
results = child.expect(expects, timeout=timeout)
|
||||
if results == 0:
|
||||
results = child.expect(expects[self.protocol], timeout=timeout)
|
||||
results_value = expects[self.protocol][results]
|
||||
|
||||
if results in initial_indices[self.protocol]:
|
||||
if self.protocol in ["ssh", "sftp"]:
|
||||
child.sendline('yes')
|
||||
elif self.protocol == "telnet":
|
||||
if self.user != '':
|
||||
elif self.protocol in ["telnet", "kubectl"]:
|
||||
if self.user:
|
||||
child.sendline(self.user)
|
||||
else:
|
||||
self.missingtext = True
|
||||
break
|
||||
if results in [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16]:
|
||||
|
||||
elif results in error_indices[self.protocol]:
|
||||
child.terminate()
|
||||
if results == 12 and attempts != max_attempts:
|
||||
if results_value == pexpect.TIMEOUT and attempts != max_attempts:
|
||||
attempts += 1
|
||||
endloop = True
|
||||
break
|
||||
else:
|
||||
if results == 12:
|
||||
after = "Connection timeout"
|
||||
after = "Connection timeout" if results_value == pexpect.TIMEOUT else child.after.decode()
|
||||
return f"Connection failed code: {results}\n{child.before.decode().lstrip()}{after}{child.readline().decode()}".rstrip()
|
||||
|
||||
elif results in eof_indices[self.protocol]:
|
||||
if results_value == password_prompt:
|
||||
if passwords:
|
||||
child.sendline(passwords[i])
|
||||
else:
|
||||
after = child.after.decode()
|
||||
return ("Connection failed code:" + str(results) + "\n" + child.before.decode().lstrip() + after + child.readline().decode()).rstrip()
|
||||
if results == 8:
|
||||
if len(passwords) > 0:
|
||||
child.sendline(passwords[i])
|
||||
self.missingtext = True
|
||||
break
|
||||
elif results_value == "suspend":
|
||||
child.sendline("\r")
|
||||
sleep(2)
|
||||
else:
|
||||
self.missingtext = True
|
||||
break
|
||||
if results in [9, 11]:
|
||||
endloop = True
|
||||
child.sendline()
|
||||
break
|
||||
if results == 10:
|
||||
child.sendline("\r")
|
||||
sleep(2)
|
||||
endloop = True
|
||||
child.sendline()
|
||||
break
|
||||
|
||||
if endloop:
|
||||
break
|
||||
if results == 12:
|
||||
if results_value == pexpect.TIMEOUT:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
child.readline(0)
|
||||
self.child = child
|
||||
return True</code></pre>
|
||||
@ -3208,6 +3311,14 @@ def run(self, commands, vars = None,*, folder = '', prompt = r'>$
|
||||
connect = self._connect(timeout = timeout)
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
||||
if connect == True:
|
||||
# Attempt to set the terminal size
|
||||
try:
|
||||
self.child.setwinsize(65535, 65535)
|
||||
except Exception:
|
||||
try:
|
||||
self.child.setwinsize(10000, 10000)
|
||||
except Exception:
|
||||
pass
|
||||
if "prompt" in self.tags:
|
||||
prompt = self.tags["prompt"]
|
||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||
@ -3336,6 +3447,14 @@ def test(self, commands, expected, vars = None,*, prompt = r'>$|#$|\$$|&g
|
||||
'''
|
||||
connect = self._connect(timeout = timeout)
|
||||
if connect == True:
|
||||
# Attempt to set the terminal size
|
||||
try:
|
||||
self.child.setwinsize(65535, 65535)
|
||||
except Exception:
|
||||
try:
|
||||
self.child.setwinsize(10000, 10000)
|
||||
except Exception:
|
||||
pass
|
||||
if "prompt" in self.tags:
|
||||
prompt = self.tags["prompt"]
|
||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||
|
Reference in New Issue
Block a user