Goal
I'm trying to automate a fortigate configuration change for a couple dozen routers and am not winning. Have tried Python's paramiko library, Python fabric and Perl's expect and Rex interfaces/libraries.
Other info
* Routers: Fortigate 60D
* Firmware: v5.0,build0252 (GA Patch 5)
* SSH enabled: True
I can log in over SSH and run these commands manually!
I used the perl expect library with Fortigate 60B's in the past but it no longer works. Before I share the code I want to ask:
Is there some new feature in Fortigate's that prevents this type of automation?
A simple and harmless command to test [ list current dhcp leases ]:
execute dhcp lease-list wifi
Code
Perl/Expect:
my $timeout = 10;
$ssh->expect($timeout, [ qr/password: /i ]);
$ssh->send("$passwd\r\n");
$ssh->expect($timeout, [ qr/#/i ]);
$ssh->send("execute dhcp lease-list wifi\r");
$ssh->expect($timeout, [ qr/#/i ]);
$ssh->send("exit\r");
$ssh->soft_close();
Output: none
Perl/Rex:
desc "List all dhcp leases";
task "leases", group => "forti", sub {
my $output = run "execute dhcp lease-list wifi";
say $output;
};
Output:
[2014-02-11 13:14:48] (30011) - INFO - Running task: leases
[2014-02-11 13:14:48] (30022) - INFO - Connecting to 10.10.10.2 (admin)
[2014-02-11 13:14:49] (30022) - INFO - Connected to 10.10.10.2, trying to authenticate.
Fortigate # Unknown action 0
Fortigate #
Python/paramiko:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('10.10.10.2',username='fake_root',password='fake_pass')
stdin, stdout, stderr=ssh.exec_command("execute dhcp lease-list wifi")
stdout.readlines()
ssh.close()
Output: none
Python/Fabric:
def view_dhcp_leases():
print("Viewing dhcp leases")
run("execute dhcp lease-list wifi")
Output:
[10.10.10.2] Executing task 'view_dhcp_leases'
Viewing dhcp leases
[10.10.10.2] run: execute dhcp lease-list wifi
[10.10.10.2] out: Fortigate # Unknown action 0
[10.10.10.2] out:
[10.10.10.2] out: Fortigate #
Done.
Disconnecting from 10.10.10.2 ... done.
Conclusions ...so far
Unknown action 0 means, "I don't know this command [ in this context ]". This command can be run manually at the first prompt. Also, as you can see in the fabric and rex examples: it does authenticate and connect! I conclude that this is by design for security reasons ...and more likely to sell their proprietary management crap.
This works for me on a FortiNet Mail Appliance.
from Exscript.util.interact import Account
from Exscript.protocols import SSH2
account = Account('USERNAME', 'PASSWORD')
conn = SSH2()
conn.connect('IP')
conn.login(account)
conn.execute('COMMAND')
conn.send('exit \r')
conn.close()
https://github.com/knipknap/exscript
The following script worked for me against a FortiGate (5.2.4) with Python/Paramiko:
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('1.1.1.254',username='admin',password='password')
stdin, stdout, stderr=ssh.exec_command("get system status")
type(stdin)
stdout.readlines()
Andy
I have 60B.
Please try to run this command from linux terminal.
If you don't have sshpass - you can install it.
sshpass -p 'adminpassword' ssh ip_of_fw -l admin execute dhcp lease-list
If you want to use fabric to run commands on fortigates you need to disable the shell wrapping used by fabric when connecting via SSH:
from fabric.api import run
def get_sys():
run("get sys status",shell=False)
Youc can upgrade an OS version and then use API with P
Related
ssh -R 80:localhost:8080 nokey#localhost.run is equivalent to what in Paramiko?
I search a lot but no success ...
Code I tried
import paramiko
command = "df"
# Update the next three lines with your
# server's information
username = "localhost:9876"
host = "nokey#localhost.run"
client = paramiko.client.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host,port=80, username=username)
_stdin, _stdout,_stderr = client.exec_command("df")
print(stdout.read().decode())
client.close()
A partial equivalent is Transport.request_port_forward.
But that will only setup the server-side part of the forwarding. The local-side implementation that will establish the local connection from the SSH client (Paramiko) to the local server is up to you.
An example how to implement it is given in demos/rforward.py.
An equivalent of your command will be:
python3 rforward.py -u nokey -p 80 -r 127.0.0.1:8080 localhost.run
Note that the rforward.py script does not start a shell. It just does the forwarding. So it's actually an equivalent of ssh with -N switch.
If you need both forwarding and shell, you will need to modify the script to additionally do SSHClient.invoke_shell like demos/demo.py do.
I'm trying to automate getting the status of 500 servers.
I can ssh into the server bmc console which brings up a smash clp terminal using a valid username and password and run the command: chassis --get power status and this returns on screen: The host status is on
I have done pssh to the server but in the case of the server being off I would like to pssh through the BMC console and run the same command to check if the server is available. When I use the same method for pssh on the console that I do on the server nothing happens. There is no return no error it sits there executing the script until I force it to stop.
How can I pssh to the smash clp?
My simple test script:
from pssh.clients import ParallelSSHClient
hosts = ['server-con']
username='uname'
password='password'
client = ParallelSSHClient(hosts, username, password)
output = client.run_command('chassis --get power status')
for host, host_output in output.items():
for line in host_output.stdout:
print(line)
I don't know BMC console or chassis, but from my understanding, you are trying to wrap in python ssh command to run remotely and having a problem with that.
My suggestion is to simply use subprocess and run it
output = subprocess.run("ssh foo chassis --get power status", shell=True, stdout=subprocess.PIPE)
I have a problem with a test suite. I use robot framework and python.I created a function in python which executes a console command in a remote Linux client.
def purge_default_dns(device_ip):
ssh_val = "usr1#" + device_ip
command = "ctool dns | grep -v \"grep\" | awk \'{print $2}\'"
test = check_output(["ssh", ssh_val, "-p", "6022", command])
The check_output() function connects with device_ip and executes command. If I try to connect with a fully qualified domain name (ex. my.domain.io), then I get a prompt for password (which is empty). I press enter and command executes regular. Is there any parameter that passes for example Enter when password prompt comes up?
I tried ssh -e switch , I don't want to change ssh client , I just need a generic solution.
For example using paramiko library in the code below , I can create an paramiko SSHClient , which has a parameter for password and doesn't prompt anything. While I can't use paramiko right now , I need something else with SSHLirary to go around the problem.
def send_ssh_command(device_ip , command):
hostname = device_ip
password = ""
username = "usr1"
port = 6022
try:
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy())
client.connect(hostname, port=port, username=username, password=password)
stdin , stdout , stderr = client.exec_command(command)
command_return_val = stdout.read()
finally:
client.close()
return command_return_val
Thank you.
To get this straight, the only solution you look for is to pass the password on the command line to the default OS ssh client, and do not/cannot install any libraries (paramiko, etc) that can help you achieve the same result through other means?
I'm asking this, because the robot framework's SSHLibrary provides this out of the box; you already have the python's solution with paramiko; and the general linux solution is to install the sshpass package, and use it to pass the value:
sshpass -p "YOUR_PASS" ssh -usr1#my.domain.io:6022
So if all of these are not an option, you are left with two alternatives - either hack something around SSH_ASKPASS - here's an article with a sample, or use expect to pass it - this one is what I'd prefer out of the two.
Here's a very good SO answer with an expect script wrapper around ssh. In your method, you will have to first create a file with its content, set an executable flag on it, and then call that file in check_output(), passing as arguments the password, 'ssh' and all its arguments.
Why You need to go with python , I am using below code in robotframework for the same:
[Arguments] ${host}=${APP_SERVER} ${username}=${APP_USERNAME} ${password}=${APP_PASSWORD}
Open Connection ${host} timeout=2m
Login ${username} ${password}
${out} ${err} ${rc}= Execute Command cd ${PATH};ctool dns | grep -v \"grep\" | awk \'{print $2}\' * return_stdout=True return_stderr=True return_rc=True
Should Be Equal ${rc} ${0}
I use Paramiko for establishing SSH connection with some target device and I want to execute reboot command.
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(zip_hostname, username=username, password=password, timeout=1)
try:
stdin, stdout, stderr = ssh.exec_command("/sbin/reboot -f")
# .........
# some code
# .........
except AuthenticationException, e:
print ''
finally:
ssh.close()
But after executing ssh.exec_command("/sbin/reboot -f") "some code" does not execute because program is stuck in exec_command (the disconnection takes place caused by rebooting). What should I do to solve my problem?
Try this:
ssh.exec_command("/sbin/reboot -f > /dev/null 2>&1 &")
All the output of reboot is redirected to /dev/null to make it produce no output and it is started in the background thanks to the '&' sign in the end. Hopefully the program won't hang on that line this way, because the remote shell gives the prompt back.
Get the transport from the ssh and set the keepalive using:
transport = ssh.get_transport()
transport.set_keepalive(5)
This sets the keepalive to 5 seconds; mind you I would have expected the timeout=1 to have achieved the same thing.
All you need to do is to call channel.exec_command() instead of the high-level interface client.exec_command()
# exec fire and forget
timeout=0.5
transport = ssh.get_transport()
chan = ssh.get_transport().open_session(timeout=timeout)
chan.settimeout(timeout)
try:
chan.exec_command(command)
except socket.timeout:
pass
I was having this issue and managed to avoid it by switching to this command:
/sbin/shutdown -r now
Note this command does not result in any STDOUT or STDERR output
In case you or anyone else gets stuck trying to reboot host with sudo using forwarding agents (ssh keys) or in my case (yubikey)
If you look at this as bash you would reboot a host as non root user like this.
ssh -t -A user#hostname sudo /sbin/reboot
For the -A flag, from ssh man page
Enables forwarding of the authentication agent connection. This can also be specified on a per-host basis in a
configuration file.
Agent forwarding should be enabled with caution. Users with the ability to bypass file permissions on the
remote host (for the agent’s Unix-domain socket) can access the local agent through the forwarded connection.
An attacker cannot obtain key material from the agent, however they can perform operations on the keys that
enable them to authenticate using the identities loaded into the agent.*
For the -t flag, from ssh man page
Force pseudo-tty allocation. This can be used to execute arbitrary screen-based programs on a remote machine,
which can be very useful, e.g. when implementing menu services. Multiple -t options force tty allocation, even
if ssh has no local tty.*
So lets break this down into how you would do this in paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=host, username=username)
s = ssh.get_transport().open_session()
paramiko.agent.AgentRequestHandler(s)
ssh.exec_command("sudo /sbin/reboot", get_pty=True)
For authentication forwarding (-A flag in bash ssh command) for paramiko
ssh = paramiko.SSHClient() #'ssh' is client variable
s = ssh.get_transport().open_session() #get 'ssh' transport and open sessions assigned to 's' variable
paramiko.agent.AgentRequestHandler(s) #call in 's' to the forwarding agent for current ssh session
Now for force pseudo-tty allocation (-t flag in bash ssh command) for paramiko
ssh.exec_command("sudo /sbin/reboot", get_pty=True)
Adding 'get_pty=True' to exec_command will allow you execute sudo /sbin/reboot
Hope this helps, everyone's environments are different but this should work as it the exact same thing as if you ran it as bash.
I need to create tunneling to read information from a database. I use Paramiko, but I have not worked with tunneling yet. Please provide an example of a simple code that creates and closes a tunnel.
At work we usually create ssh tunnels forwarding ports. The way we do that is, by using the standard command ssh -L port:addr:port addr with subprocess running in a separate thread.
I found this useful link: https://github.com/paramiko/paramiko/blob/master/demos/forward.py with an example of doing port forwarding with paramiko.
I used sshtunnel for my projects. Example of the forwarding remote local MySQL port to the host local port:
pip install sshtunnel
python -m sshtunnel -U root -P password -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
Even though this does not use paramiko, I believe it's a very clean solution to implement (similar to #dario's answer but without managing the thread in python).
There's this little-mentioned feature in openssh client that allows us to control a ssh process through a unix socket, quoting man ssh:
-M Places the ssh client into “master” mode for connection sharing. Multiple -M options places ssh
into “master” mode with confirmation required before slave connections are accepted. Refer to the
description of ControlMaster in ssh_config(5) for details.
-S ctl_path
Specifies the location of a control socket for connection sharing, or the string “none” to disable
connection sharing. Refer to the description of ControlPath and ControlMaster in ssh_config(5)
for details.
So you can start background process of ssh (with -Nf) and then check (or terminate) it with a another ssh call.
I use this in a project that requires a reverse tunnel to be established
from subprocess import call, STDOUT
import os
DEVNULL = open(os.devnull, 'wb')
CONFIG = dict(
SSH_SERVER='ssh.server.com',
SSH_PORT=2222,
SSH_USER='myuser',
SSH_KEY='/path/to/user.key',
REMOTE_PORT=62222,
UNIX_SOCKET='/tmp/ssh_tunnel.sock',
KNOWN_HOSTS='/path/to/specific_known_host_to_conflicts',
)
def start():
return call(
[
'ssh', CONFIG['SSH_SERVER'],
'-Nfi', CONFIG['SSH_KEY'],
'-MS', CONFIG['UNIX_SOCKET'],
'-o', 'UserKnownHostsFile=%s' % CONFIG['KNOWN_HOSTS'],
'-o', 'ExitOnForwardFailure=yes',
'-p', str(CONFIG['SSH_PORT']),
'-l', CONFIG['SSH_USER'],
'-R', '%d:localhost:22' % CONFIG['REMOTE_PORT']
],
stdout=DEVNULL,
stderr=STDOUT
) == 0
def stop():
return __control_ssh('exit') == 0
def status():
return __control_ssh('check') == 0
def __control_ssh(command):
return call(
['ssh', '-S', CONFIG['UNIX_SOCKET'], '-O', command, 'x'],
stdout=DEVNULL,
stderr=STDOUT
)
-o ExitOnForwardFailure=yes makes sure the ssh command will fail if the tunnel cannot be established, otherwise it will not exit.
Might I suggest trying something like pyngrok to programmatically manage an ngrok tunnel for you? Full disclosure, I am the developer of it. SSH example here, but it's as easy as installing pyngrok:
pip install pyngrok
and using it:
from pyngrok import ngrok
# <NgrokTunnel: "tcp://0.tcp.ngrok.io:12345" -> "localhost:22">
ssh_tunnel = ngrok.connect(22, "tcp")
I used paramiko for some project I had a year ago, here is the part of my code where I connected with another computer/server and executed a simple python file:
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='...', username='...', password='...')
stdin, stdout, stderr = ssh.exec_command('python hello.py')
ssh.close()
stdin, stdout and sdterr contain the inputs/outputs of the command you executed.
From here, I think you can make the connection with the database.
Here is some good information about paramiko.