I'm using a 3 hop SSH with netmiko in Python and it does not like switching between the 2nd and 3rd hops - netmiko

My topology is: Laptop -> 1st Jumphost (my company) -> 2nd jumphost (my clients company) -> Various network devices (my clients network devices).
The network devices are only accessible from the 2nd jumphost, and the 2nd jumphost is only accessible from the 1st jumphost. So I'm using netmiko in Python to achieve this. My code is below.
The 1st block of code SSH's to the 1st jumphost, and then from there SSH's to the 2nd jumphost. This works correctly.
The 2nd block of code then opens a text file containing the hostnames or IP's of the individual network devices that need to be queried. For each host in that file, it SSH's to it, issues the "show version" command and then disconnects from the device (using "exit") so that the session is returned to the 2nd jumphost, ready for the next device in the file.
This works correctly for the very first device, but crashes upon the "output = device.send_command('exit')" line. Netmiko claims that the pattern is not detected. I think I understand why, because netmiko is using the name in the hostname prompt, when this changes back to the 2nd jumphost hostname upon the disconnect it gets confused and throws an error. If this is the case I have 2 questions:
How come it copes OK when moving from the 1st jumphost to the 2nd jumphost AND from the 2nd jumphost to the network device. In both of these cases the hostname prompt also changes...
What's the solution? How can I safely move between the 2nd jumphost and network devices in order to achieve the loop?
from netmiko import ConnectHandler
import time
jump1 = "x.x.x.x"
jump2 = "y.y.y.y"
jump1_username = "myusername"
jump1_password = "mypassword"
jump2_username = "myusername"
jump2_password = "mypassword"
jump_type = "linux"
cmd_jump = "ssh " + jump2_username + "#" + jump2 + "\n"
device = ConnectHandler(device_type=jump_type, ip=jump1, username=jump1_username, password=jump1_password) # ssh to 1st jumphost
output = device.send_command('cat /proc/sys/kernel/hostname') #just shows me that login worked
print(output, flush=True) # just shows me that login worked
device.write_channel(cmd_jump) # enters ssh command for 2nd jumphost
time.sleep(1)
device.write_channel(jump2_password + "\n") # enters password for 2nd jumphost
time.sleep(1)
output = device.send_command('cat /proc/sys/kernel/hostname') #just shows me that second login worked
print(output, flush=True) # just shows me that second login worked
host_list = open(r'C:\device_list.txt','r') # a simple list of IP addresses you want to connect to each one on a new line
for host in host_list: # loop through network devices
host = host.strip()
cmd_device = "ssh " + host
device.write_channel(cmd_device + "\n") # ssh to each device
time.sleep(1)
device.write_channel(jump2_password + "\n") # enter ssh password (credientals are the same as the 2nd jumphost)
time.sleep(1)
output = device.send_command('sh ver') # run show version command
print(output, flush=True)
output = device.send_command('exit') ' disconnect from network device to return to the 2nd jumphost
time.sleep(1)
print(output, flush=True)

Ignore me, I've solved my own problem. It's because I wasn't using the "write_channel" to enter the 'exit' command. Doh!

Related

rsyslogd does not write data to logfile when configured with TLS

I'm trying to set up rsyslog with TLS to forward specific records from /var/log/auth.log from host A to a remote server B.
The configuration file I wrote for rsyslog is the following:
$DefaultNetstreamDriverCAFile /etc/licensing/certificates/ca.pem
$DefaultNetstreamDriverCertFile /etc/licensing/certificates/client-cert.pem
$DefaultNetstreamDriverKeyFile /etc/licensing/certificates/client-key.pem
$InputFilePollInterval 10
#Read from the auth.log file and assign the tag "ssl-auth" for its messages
input
(type="imfile"
File="/var/log/auth.log"
reopenOnTruncate="on"
deleteStateOnFileDelete="on"
Tag="ssl-auth")
$template auth_log, " %msg% "
# Send ssl traffic to server on port 514
if ($syslogtag == 'ssl-auth') then{action
(type="omfwd"
protocol="tcp"
target="<ip#server>"
port="514"
template="auth_log"
StreamDriver="gtls"
StreamDriverMode="1"
StreamDriverAuthMode="x509/name"
)}
Using this configuration, when I try to ssh-login the first time into the host A from another host X everything works fine; the file /var/log/auth.log is written and the tcpdump shows traffic towards server B.
But from then on, it does not work anymore.
Even if I try to exit from host A and login back again whenever I do, the file /var/log/auth.log is not ever written and no traffic appears over tcpdump.
The very strange things is that if I remove the TLS from the configuration it works.

Asyncio RuntimeError: readuntil() called while another coroutine is already waiting for incoming data

Using python3.6.8:
I'm attempting to script the initial configuration of network devices on boot. My script opens telnet connection on "ip_addr:port". Once connected, script stimulates the network device it's connecting to with "\n\n" (simulating two 'Enter' input from an admin).
connection = asyncio.open_connection(
ip_addr,
port,
)
try:
reader, writer = await asyncio.wait_for(connection, 5)
print(f"successfully connected to {ip_addr}:{port}")
writer.write(b'\n\n\n')
Some devices are already configured and I except " login: " to show up in the read buffer upon entering '\n'. However if the device is not configured yet, " login: " will not show up in the buffer. Therefore I thought I could use wait_for and timeout option to have this cancelled and move on with another reader.readuntil(...) expecting another output.
try:
await asyncio.wait_for(reader.readuntil(b' login: '), 3)
print(f"{ip_addr}:{port} alredy booted")
break
except (asyncio.TimeoutError, OSError):
print('Nope, moving forward')
await reader.readuntil(b'normal setup ?(yes/no)[n]: ')
However this raises a RuntimeError. Reading the documentation I excepted the task to be cancelled if the timeout is reached, so why can't it have another coroutine readuntil() ?

I have errors in a script, but only when it runs as a cron job

I have a script that runs every hour to facilitate port forwarding with openvpn. It all works well when run from CLI, but fails when run through the same users cron. The part that fails is the end where it uses the value $PORT.
You can see that the values PORT and VPN_IP are not returning a value and the deluge command is failing.
Here is the result run directly:
Your VPN ipaddress is 10.107.1.6
Contacting PIA for port forwarding .......
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 106 100 14 100 92 4 30 0:00:03 0:00:03 --:--:-- 30
Port forwarding is currently using port 37186
Changing port settings on deluge....
Setting random_port to False..
Configuration value successfully updated.
Setting listen_ports to (37186, 37186)..
and here is the same script run through cron
crontab:
34 * * * * bash /home/alleyoopster/scripts/pia_portforward.sh > /home/alleyoopster/pia_port.log 2>&1
Result with no VPN address or Port address returned and errors:
Your VPN ipaddress is
Contacting PIA for port forwarding .......
Port forwarding is currently using port
Changing port settings on deluge....
Setting random_port to False..
Configuration value successfully updated.
malformed expression (,)
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/deluge/ui/console/main.py", line 344, in do_command
ret = self._commands[cmd].handle(*args, **options.__dict__)
File "/usr/lib/python2.7/dist-packages/deluge/ui/console/commands/config.py", line 104, in handle
return self._set_config(*args, **options)
File "/usr/lib/python2.7/dist-packages/deluge/ui/console/commands/config.py", line 140, in _set_config
val = simple_eval(options["set"][1] + " " .join(args))
File "/usr/lib/python2.7/dist-packages/deluge/ui/console/commands/config.py", line 87, in simple_eval
res = atom(src.next, src.next())
File "/usr/lib/python2.7/dist-packages/deluge/ui/console/commands/config.py", line 56, in atom
out.append(atom(next, token))
File "/usr/lib/python2.7/dist-packages/deluge/ui/console/commands/config.py", line 79, in atom
raise SyntaxError("malformed expression (%s)" % token[1])
SyntaxError: malformed expression (,)
#! /bin/sh
#Simple bash script to facilitate Port Forwarding use with openvpn and PIA
#Use as a cron job to run every hour
#This script will also change the port in deluge. It needs deluge-console installed
#Transmission should also work with the correct commands
#YOUR SETTINGS
#Private Internet Access Username and Password here
USERNAME="username"
PASSWORD="password"
#Enter the correct tun here. Normally tun0. The command ifconfig will list your network config
TUN="tun0"
#Get the local ip address
VPN_IP=$(ifconfig $TUN|grep -oE "inet addr: *10\.[0-9]+\.[0-9]+\.[0-9]+"|tr -d "a-z :")
echo "Your VPN ipaddress is " $VPN_IP
echo Contacting PIA for port forwarding .......
TMP_PORT=$(curl -d "user=$USERNAME&pass=$PASSWORD&client_id=$(cat ~/.pia_client_id)&local_ip=$VPN_IP" https://www.privateinternetaccess.com/vpninfo/port_forward_assignment)
PORT=$(echo $TMP_PORT | sed "s/[^0-9]*//g")
echo "Port forwarding is currently using port "$PORT
echo "Changing port settings on deluge...."
deluge-console "config --set random_port False"
deluge-console "config --set listen_ports ($PORT,$PORT)"
It sounds like the PATH setting in your cron job doesn't match your user's PATH, and cron may not be finding the ifconfig command so that it can obtain the VPN IP address.
Either specify the full path to /sbin/ifconfig to get the local IP address, or add the following line at the top of your crontab (I'm just listing standard paths - adjust as necessary to suit your setup):
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

Loop for checking string change in system function output (monitoring a DNS update)

I am switching DNS servers and I'd like to write a short ruby script that runs every 10s and triggers a local Mac OS X system notification as soon as my website resolves to a different IP.
Using terminal-notifier sending a system notification is as easy as this
terminal-notifier -message "DNS Changed"
I'd like to trigger it as soon as the output of
ping -i 10 mywebsite.com
... changes or simply does not contain a defined IP string anymore.
> 64 bytes from 12.34.56.789: icmp_seq=33 ttl=41 time=241.564 ms
in this case "12.34.56.789".
How do I monitor the change of the output string of the ping -i 10 mywebsite.com and call the notification function once a change has been detected?
I thought this might be a nice practice while waiting for the DNS to be updated.
Try this:
IP = "12.34.56.789"
p = IO.popen("ping -i 10 mywebsite.com")
p.each_line do |l|
if(! l =~ /from #{IP}/) #The IP has changed
system("terminal-notifier -message \"DNS Changed\"")
end
end

Sending an email from R using the sendmailR package

I am trying to send an email from R, using the sendmailR package. The code below works fine when I run it on my PC, and I recieve the email. However, when I run it with my macbook pro, it fails with the following error:
library(sendmailR)
from <- sprintf("<sendmailR#%s>", Sys.info()[4])
to <- "<myemail#gmail.com>"
subject <- "TEST"
sendmail(from, to, subject, body,
control=list(smtpServer="ASPMX.L.GOOGLE.COM"))
Error in socketConnection(host = server, port = port, blocking = TRUE) :
cannot open the connection
In addition: Warning message:
In socketConnection(host = server, port = port, blocking = TRUE) :
ASPMX.L.GOOGLE.COM:25 cannot be opened
Any ideas as to why this would work on a PC, but not a mac? I turned the firewall off on both machines.
Are you able to send email via the command-line?
So, first of all, fire up a Terminal and then
$ echo “Test 123” | mail -s “Test” user#domain.com
Look into /var/log/mail.log, or better use
$ tail -f /var/log/mail.log
in a different window while you send your email. If you see something like
... setting up TLS connection to smtp.gmail.com[xxx.xx.xxx.xxx]:587
... Trusted TLS connection established to smtp.gmail.com[xxx.xx.xxx.xxx]:587:\
TLSv1 with cipher RC4-MD5 (128/128 bits)
then you succeeded. Otherwise, it means you have to configure you mailing system. I use postfix with Gmail for two years now, and I never had have problem with it. Basically, you need to grab the Equifax certificates, Equifax_Secure_CA.pem from here: http://www.geotrust.com/resources/root-certificates/. (They were using Thawtee certificates before but they changed last year.) Then, assuming you used Gmail,
Create relay_password in /etc/postfix and put a single line like this (with your correct login and password):
smtp.gmail.com login#gmail.com:password
then in a Terminal,
$ sudo postmap /etc/postfix/relay_password
to update Postfix lookup table.
Add the certificates in /etc/postfix/certs, or any folder you like, then
$ sudo c_rehash /etc/postfix/certs/
(i.e., rehash the certificates with Openssl).
Edit /etc/postfix/main.cf so that it includes the following lines (adjust the paths if needed):
relayhost = smtp.gmail.com:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/relay_password
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = may
smtp_tls_CApath = /etc/postfix/certs
smtp_tls_session_cache_database = btree:/etc/postfix/smtp_scache
smtp_tls_session_cache_timeout = 3600s
smtp_tls_loglevel = 1
tls_random_source = dev:/dev/urandom
Finally, just reload the Postfix process, with e.g.
$ sudo postfix reload
(a combination of start/stop works too).
You can choose a different port for the SMTP, e.g. 465.
It’s still possible to use SASL without TLS (the above steps are basically the same), but in both case the main problem is that your login informations are available in a plan text file... Also, should you want to use your MobileMe account, just replace the Gmail SMTP server with smtp.me.com.

Resources