Python service to consume Ambient Weather API data
Continuing from the last post (Handling data from Ambient Weather WS-2902C to MQTT), we have a working script that reads the data coming from the Ambient Weather WS-2902 base station (Ambient Weather API) and sends it to a MQTT broker. In this post, we will turn that script into a Linux service that starts at boot and runs forever. This type of thing is perfect for Linux. Non-server Windows versions aren’t really appropriate for this since they reboot often with updates and such. If you want to run Windows Server, more power to you, you probably don’t need this guide. We will thus be focusing on Linux for consuming the Ambient Weather API. A Raspberry Pi is a perfect device for this (plenty of power, as big as a credit card, and less than $100).
Linux Services
Linux is made up of many individual components. Each component is designed to handle a single task (more or less). This differs from Windows where there are large executables/processes that handle many tasks. We will be taking advantage of Linux’s single task theory to add a new service that will run the Python script for ingesting and consuming the Ambient Weather API. In this instance for Austin’s Nerdy Things, the weather data is being provided by the Ambient Weather WS-2902C weather station.
Creating the service
For Ubuntu/Debian based distributions, service files live under /etc/systemd/system. Here is a list of services on the container I’m utilizing.
In this file we need to define our service. The After lines mean don’t start this up until the listed services are running. The rest is pretty straight forward. I’m not 100% sure what the WantedBy line is for but it’s present in most of my service files. The contents of ambient-weather-api.service are as follows:
[Unit]
Description=Python script to ingest Ambient Weather API data
After=syslog.target
After=network.target
[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/bin/python3 /srv/ambient-weather-api/main.py
ExecStartPre=/bin/sleep 5
Restart=always
RestartSec=5s
# Give the script some time to startup
TimeoutSec=10
[Install]
WantedBy=multi-user.target
Save the file. The service definition is looking for Python at /usr/bin/python3 and the python script at /srv/ambient-weather-api/main.py. You will probably be fine with the Python executable, but be sure to mv or cp the main.py file to /srv/ambient-weather-api/main.py.
We will need to reload the service definitions:
systemctl daemon-reload
Now we can start the service:
systemctl start ambient-weather-api.service
And verify it is running:
systemctl status ambient-weather-api.service
The above screenshot shows it is indeed running and active. It is still showing the print messages in the log as well, which we should disable by commenting out the lines by adding a # in front of the print line. In this case, it is coming from line 56 (the status check in the publish function).
In my introduction post, I said I would write about topics in order of interest. Securing WordPress blogs from hackers isn’t exactly fun or interesting but it is very necessary in this day and age. Hackers are constantly probing sites on the internet for insecurities. They’re constantly trying to log into WordPress sites with easily guessed passwords (hint: don’t use ‘password’ as your password). Here are some hints on how to secure WordPress blogs from hackers.
If you prefer a video version, check out my first ever YouTube video (!) covering this same content here – https://youtu.be/wKgm_684acM.
When I set this site up, the first 24 hours were pretty quiet. After that, the attacks started ramping up. I decided to take action and lock down access. There are three main things I did to secure this WordPress blog installation and VPS it is hosted on:
Disable password-based SSH authentication for logins
Install and enable Fail2Ban
Install WordPress specific Fail2Ban filters
#1 – Disable password-based SSH authentication
Step 0 – Enable SSH Key Authentication
Before you disable password-based authentication, you need to enable SSH key based authentication. I have posted a SSH key tutorial here – SSH Key Tutorial.
Password-based SSH authentication
SSH stands for secure shell. It is how 99% of Linux/Unix servers on the public internet and private intranets are administered. There are two main methods of logging in with SSH: 1) password and 2) key. Password is pretty straight-forward and is what most people are familiar with. You have a username and password. If you enter the right password for the username, you get in. Hackers are constantly testing common usernames (root, admin, user, guest) with common passwords (password, password1, password123, test, etc.). Further – they aren’t testing just one combination of user/pass at a time, they keep trying passwords until they give up or are banned. I had my VPS for a few weeks before activating austinsnerdythings.com on it and here is a random sample starting a minute after midnight for about six minutes: $sudo head -n 100 /var/log/auth.log.1
Feb 28 00:01:52 austinsnerdythings.com sshd[2265571]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.184.14.90 user=root
Feb 28 00:01:54 austinsnerdythings.com sshd[2265571]: Failed password for root from 222.184.14.90 port 45182 ssh2
Feb 28 00:01:54 austinsnerdythings.com sshd[2265571]: Received disconnect from 222.184.14.90 port 45182:11: Bye Bye [preauth]
Feb 28 00:01:54 austinsnerdythings.com sshd[2265571]: Disconnected from authenticating user root 222.184.14.90 port 45182 [preauth]
<snip>
Feb 28 00:04:59 austinsnerdythings.com sshd[2265587]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=139.198.121.63 user=root
Feb 28 00:05:02 austinsnerdythings.com sshd[2265587]: Failed password for root from 139.198.121.63 port 53437 ssh2
Feb 28 00:05:04 austinsnerdythings.com sshd[2265587]: Connection closed by authenticating user root 139.198.121.63 port 53437 [preauth]
Feb 28 00:06:06 austinsnerdythings.com sshd[2265591]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=212.64.38.8 user=root
Feb 28 00:06:07 austinsnerdythings.com sshd[2265591]: Failed password for root from 212.64.38.8 port 37354 ssh2
Feb 28 00:06:08 austinsnerdythings.com sshd[2265591]: Received disconnect from 212.64.38.8 port 37354:11: Bye Bye [preauth]
Feb 28 00:06:08 austinsnerdythings.com sshd[2265591]: Disconnected from authenticating user root 212.64.38.8 port 37354 [preauth]
<snip>
Feb 28 00:06:48 austinsnerdythings.com sshd[2265595]: Received disconnect from 49.88.112.118 port 37056:11: [preauth]
Feb 28 00:06:48 austinsnerdythings.com sshd[2265595]: Disconnected from 49.88.112.118 port 37056 [preauth]
Feb 28 00:06:56 austinsnerdythings.com sshd[2265589]: Connection reset by 49.88.112.118 port 53318 [preauth]
Feb 28 00:08:00 austinsnerdythings.com sshd[2265597]: Received disconnect from 49.88.112.118 port 61081:11: [preauth]
Feb 28 00:08:00 austinsnerdythings.com sshd[2265597]: Disconnected from authenticating user root 49.88.112.118 port 61081 [preauth]
Each login attempt is 3-4 lines, so that’s 10 attempts in 6 minutes. Also notice the repeating IP addresses – 49.88.112.118 tried 4 separate times to log in across 6 minutes!
Hackers try user/pass logins because they’re relatively easy. And they get lucky often enough it is worth it.
Key-based SSH authentication
The other method to logging in with SSH is via public/private key. How this works is you generate a public/private keypair. Then you put the contents of the public key on the server you want to log in to. When logging in, your SSH client says “hello, I am user austin and I have a key to login and here it is”! The public key that’s copied to the remote server looks like this:
As you might imagine, it’s a lot harder to guess that key than it is a password. In fact, cracking a 2048 bit key like the one above would take 300 trillion years with a quantum supercomputer (which doesn’t yet exist)! Source. The universe is 15 billion years old. That means it would require 300 trillion / 15 billion = 20,000 universe lifetimes to crack.
Before you disable password-authentication, you need to be 100% sure that key-based authentication is working or else you will lock yourself out of your server!
To disable password-based authentication, you need to edit /etc/ssh/sshd_config, find PasswordAuthentication and put no after it. If it is commented out (there is a # at the front of the line) delete the #. It will look like this when finished:
Then you need to restart the SSH daemon (service) for the change to take effect:sudo systemctl restart ssh.service. Now you password-based SSH authentication has been disabled!
My failed authentication attempts dropped dramatically after disabling password-based SSH authentication. Below is the same general timeframe from the morning of when this post was written:
Mar 13 00:00:24 austinsnerdythings.com sshd[108357]: Invalid user ftpuser from 167.99.34.31 port 59060
Mar 13 00:00:24 austinsnerdythings.com sshd[108357]: Received disconnect from 167.99.34.31 port 59060:11: Normal Shutdown, Thank you for playing [preauth]
Mar 13 00:00:24 austinsnerdythings.com sshd[108357]: Disconnected from invalid user ftpuser 167.99.34.31 port 59060 [preauth]
Mar 13 00:03:09 austinsnerdythings.com sshd[108549]: Received disconnect from 24.8.45.4 port 5402:11: disconnected by user
Mar 13 00:03:09 austinsnerdythings.com sshd[108549]: Disconnected from user austin 24.8.45.4 port 5402
Mar 13 00:03:09 austinsnerdythings.com sshd[108438]: pam_unix(sshd:session): session closed for user austin
Mar 13 00:12:33 austinsnerdythings.com sshd[108934]: Invalid user postgres from 167.99.34.31 port 46444
Mar 13 00:12:33 austinsnerdythings.com sshd[108934]: Received disconnect from 167.99.34.31 port 46444:11: Normal Shutdown, Thank you for playing [preauth]
Mar 13 00:12:33 austinsnerdythings.com sshd[108934]: Disconnected from invalid user postgres 167.99.34.31 port 46444 [preauth]
Mar 13 00:12:44 austinsnerdythings.com sshd[108941]: Received disconnect from 222.187.232.213 port 11758:11: [preauth]
Mar 13 00:12:44 austinsnerdythings.com sshd[108941]: Disconnected from authenticating user root 222.187.232.213 port 11758 [preauth]
Mar 13 00:17:40 austinsnerdythings.com sshd[109097]: Received disconnect from 221.131.165.23 port 32827:11: [preauth]
Mar 13 00:17:40 austinsnerdythings.com sshd[109097]: Disconnected from authenticating user root 221.131.165.23 port 32827 [preauth]
Mar 13 00:24:51 austinsnerdythings.com sshd[109322]: Invalid user postgres from 167.99.34.31 port 33830
Mar 13 00:24:52 austinsnerdythings.com sshd[109322]: Received disconnect from 167.99.34.31 port 33830:11: Normal Shutdown, Thank you for playing [preauth]
Mar 13 00:24:52 austinsnerdythings.com sshd[109322]: Disconnected from invalid user postgres 167.99.34.31 port 33830 [preauth]
Most of these are just disconnects. The hackers see that my server is not accepting passwords and they just disconnect – they don’t even try to log in.
#2 – Install Fail2Ban
Fail2Ban is a helpful tool that monitors various logs and if it sees too many failed attempts, it will issue a ban on the offending IP address.
It is simple enough to install. First, update your package cache. On Ubuntu/Debian, this is done with apt:sudo apt update.
Then install fail2ban:sudo apt install -y fail2ban. This automatically enables Fail2ban so that it starts on boot. It has a bunch of out-of-the-box rules and will handle many services without any additional configuration. This is what my Fail2ban log looks like as of right now. This is all SSH bans. Notice that the duration is increasing for IP 167.172.170.218. The default ban duration is 10 minutes and I have it configured to double (plus some randomness) every extra attempt.
#3 – Add WordPress specific Fail2ban jails and plugin
Attempts to log into WordPress look like normal web traffic in web logs. Failed logins aren’t recorded specifically. We can change that by adding a plugin to WordPress that writes to /var/log/auth.log for a number of activities. Fail2ban monitors /var/log/auth.log for failed logins so it can act appropriately. I am using WP-Fail2Ban-Redux which does exactly what it says and without any nonsense. To finish the install, I copied the files from wp-content/plugins/wp-fail2ban-redux/config/filters and /jail to my fail2ban filter.d/ and jail.d/ folders:
Restart fail2ban so the changes take effect: sudo systemctl restart fail2ban
View all the bans in your log! Congrats, you’ve now applied some top notch security practices to your blog.
#4 – ALWAYS KEEP YOUR WORDPRESS INSTALL UPDATED
That is the entirety of #4.
#5 – To disable XMLRPC or not, that is the question
I haven’t disabled XML-RPC yet. XML-RPC is a way to programmatically interact with WordPress blogs. Hackers can use it to rapidly try user/password combinations and other things like that. Installing the WordPress specific Fail2Ban components will effectively ban offenders while still allowing access to the underlying services.
In conclusion
It isn’t too hard to make these three changes to secure your WordPress blog and doing so will increase the security drastically. If you would like assistance doing this on your site, please use the contact form to get in touch with me. Lastly, always keep your WordPress install up to date. Every so often, security researchers find holes in the base WordPress code. Automatic updates will prevent your site from being a target.