Preface (possibly useless unless you really want to know how I came up with this crazy idea)
Halfway through my bachelor degree I realized that Dropbox’s 2GB of space weren’t nearly sufficient to satisfy my “cloud needs”. So searching for an alternative I found this great project called Nextcloud. Forked from the (almost) namesake “owncloud”, Nextcloud is a PHP+SQL based application that allows you to host your own cloud, even on a simple development board like the Raspberry Pi.
Since I already used my ODROID C1 as a server, I decided it would be interesting to see if it would be able to host a Nextcloud instance with 4-5 users.
Initially the results were pretty good, for a couple of months the server could easily handle large data transfers over WebDAV (I’m talking about GBs of MATLAB simulations) and using it with multiple users was not an issue at all, however I kept noticing that sometimes the server would suddenly become unreachable. At first I didn’t worry too much about it until one day the instability became unbearable, in fact at a certain point the server would be unreachable almost 80% of the time.
At first I thought the issue was simply given by the fact that I added two additional devices to my nextcloud, however the server would crash even if nothing was really going on, so I started investigating further. It turns out that when the server became painfully slow I was receiving multiple SSH access request from unknown IP addresses. I obviously already setup fail2ban to prevent brute-force attacks, but this was apparently not enough to stop the flood since the requests were coming from different IP addresses.
Knowing that this mechanic was very similar to the one of a DDos attack I started documenting myself. First I applied some of the very useful tips that you may find here: https://javapipe.com/blog/iptables-ddos-protection/, secondly I started reading about port-knocking.
Port-knocking is a quite interesting technique to prevent unauthorized access, it literally checks for knocks on the right ports to enable/disable SSH access. It seems like this is the standard way of enabling/disabling SSH, however I didn’t like it too much since it doesn’t really work natively on PuTTY, so I decided to only implement it as a fallback method, deciding to go for a more modern approach ie. Telegram
Telegram port knocking
Since enabling/disabling ssh access doesn’t really compromise access to my personal data I decided to leave this very useful feature to a telegram bot that only my account has access to. Using the Python Telegram Bot API I wrote a Python daemon with the only purpose to check for an “enable ssh” or “disable ssh” command. After receiving the trigger the script updates the iptables rules accordingly and gives you access
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This program is dedicated to the public domain under the CC0 license.
"""
Simple Bot to control ssh access
"""
import logging
import os
import subprocess
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
def got_command(update, context):
"""Echo the user message."""
chat_id = update.effective_chat.id
command = update.message.text
# Restrict access
if chat_id != "your_chat_id":
update.message.reply_text("Access Denied!")
print (chat_id)
exit(1)
command = command.lower()
print ('Got command: %s' % command)
if "enable ssh" in command:
update.message.reply_text("Opening SSH port...");
subprocess.call(["ssh_open"]);
elif "disable ssh" in command:
update.message.reply_text("Closing SSH port...");
subprocess.call(["ssh_close"]);
def main():
"""Start the bot."""
updater = Updater("TOKEN", use_context=True)
dp = updater.dispatcher
dp.add_handler(MessageHandler(Filters.text & ~Filters.command, got_command))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
The program is pretty straightforward, it starts a thread and checks for incoming messages from a given chat_id (you obviously want to restrict access to your telegram instance only), when “enable ssh” or “disable ssh” is received a script changes the iptables rules, since the program will be ran by a user (I wouldn’t personally recommend giving root access to a telegram bot) the sudoers needs to be updated to run the scripts without password.
Essentially your /etc/sudoers file needs a line like this
username ALL=(ALL) NOPASSWD: /usr/local/bin/ssh_open, /usr/local/bin/ssh_close
.
ssh_close and ssh_open may be implemented with a firewall of your choice, using iptables the implementation is the following
ssh_close
#!/bin/bash
iptables -A INPUT -p tcp --dport 22 -j DROP
ssh_open
#!/bin/bash
/usr/bin/iptables -D INPUT -p tcp --dport 22 -j DROP
Lastly, since I want my bot to run at startup I created this systemd service to manage it:
[Unit]
Description=The Telegram BOT service
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
PIDFile=/run/bot.pid
ExecStart=/usr/local/bin/telegrambot.py
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
StandardOutput=null
[Install]
WantedBy=multi-user.target
Once you’ve done this you should be all set and you will be able to control SSH access through telegram.
As I already said, I would recommend that you set up a port knocking service as a fallback in case the telegram bot fails, especially if you’re working with a remote server. Also this solution is absolutely not viable for production system, for a professional application the best way to prevent DDos is obviously to use a VPS or rely on an external host. Additionally the code could be implemented in a more secure way by removing “sudo” and give iptables edit permission to a single user instead.
This code can be easily modified by adding additional elif statements.