Skip to content

Instantly share code, notes, and snippets.

@seinecle
Last active February 26, 2021 18:10
Show Gist options
  • Save seinecle/8a2fb506ef8ed8c4638a5834144f837c to your computer and use it in GitHub Desktop.
Save seinecle/8a2fb506ef8ed8c4638a5834144f837c to your computer and use it in GitHub Desktop.
Secure your bare metal servers in 1 minute from your local Windows machine. Just needs a private / pub key ready. Tested on Debian for Hetzner servers.
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package net.clementlevallois.sshautomator.serversecurization;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Security;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
/**
*
* @author LEVALLOIS
*/
public class Controller {
/**
* This script sets up basic security for Debian / Ubuntu bare servers:
*
* - ssh port changed from default 22 - new user created, root access
* disabled - password login disabled - pubkey authentification enabled -
* firewall allowing only for ssh connections on the designated port
*
* Tested on Hetzner Servers You need a public key ready to use this script.
* NOTE for HETZNER servers: before running this script, you should login
* first once as root to change the default password received by email
*
* Three dependencies:
*
* <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
* <dependency>
* <groupId>org.slf4j</groupId>
* <artifactId>slf4j-simple</artifactId>
* <version>1.6.2</version>
* </dependency>
*
* <dependency>
* <groupId>com.hierynomus</groupId>
* <artifactId>sshj</artifactId>
* <version>0.26.0</version>
* </dependency>
*
* <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
* <dependency>
* <groupId>org.bouncycastle</groupId>
* <artifactId>bcprov-jdk15on</artifactId>
* <version>1.60</version>
* </dependency>
*
*/
static String SERVERIP;
static String ROOTPASSWORD;
static String NEWSSHPORT;
static String NEWUSER;
static String NEWUSERPASSWORD;
static String PUBLICKEY;
static String PUBLICPUTTYKEY;
static String PATHTOPRIVATEPUTTYKEY;
public static void main(String[] args) throws IOException, InterruptedException {
/*
Loading password and other sensitive data from a file situated in [root of the project]/private/config.txt
This txt file contains these fields:
SERVERIP=199.256.etc.etc
ROOTPASSWORD=myrootpassword (don't forget that Hetzner obliges you to change it once before you can can use this script)
NEWSSHPORT=2489
NEWUSER=fancynewusername
NEWUSERPASSWORD=myfancynewuserpassword
PUBLICKEY=ssh-rsa 4J2J4K24JK24JKJ4K24 etc... (make sure it is one line, doesn't break in several lines).
PATHTOPRIVATEPUTTYKEY=path to your private key file (on Windows: C:\\whatever\\privatekey.ssh for instance)
**/
Properties properties = new Properties();
InputStream input = new FileInputStream("private/config.txt");
properties.load(input);
SERVERIP = properties.getProperty("SERVERIP");
System.out.println("serverip: " + SERVERIP);
ROOTPASSWORD = properties.getProperty("ROOTPASSWORD");
NEWSSHPORT = properties.getProperty("NEWSSHPORT");
NEWUSER = properties.getProperty("NEWUSER");
NEWUSERPASSWORD = properties.getProperty("NEWUSERPASSWORD");
PUBLICKEY = properties.getProperty("PUBLICKEY");
PUBLICPUTTYKEY = properties.getProperty("PUBLICPUTTYKEY");
PATHTOPRIVATEPUTTYKEY = properties.getProperty("PATHTOPRIVATEKEY");
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
SSHClient ssh;
ssh = new SSHClient();
ssh.addHostKeyVerifier(new PromiscuousVerifier());
// 22 is the default port number for Hetzner servers. We use it to make a first connection.
ssh.connect(SERVERIP, 22);
ssh.authPassword("root", ROOTPASSWORD);
//updating and upgrading the system
System.out.println("running apt-get update");
runCmd(ssh, "apt-get update", 2);
System.out.println("running apt-get upgrade - can take a few minutes");
runCmd(ssh, "yes | apt-get upgrade", 2);
//installing a debian tool called "rpl" that does simple text replacement in files. We need it for later.
System.out.println("running apt-get rpl");
runCmd(ssh, "yes | apt-get install rpl", 2);
//modifying the SSH port
System.out.println("copying the content of sshd_config with cat");
String sshd_config = runCmd(ssh, "cat /etc/ssh/sshd_config", 2);
//removing single quotes as they can break the echo command later on.
sshd_config = sshd_config.replace("'", "");
System.out.println("changing SSH port");
runCmd(ssh, "LANG=en_US rpl \"#Port 22\" \"Port " + NEWSSHPORT + "\" /etc/ssh/sshd_config", 2);
//Allow new user in sshd_config
if (!sshd_config.contains("AllowUsers")) {
System.out.println("sshd_config: adding AllowUsers " + NEWUSER);
runCmd(ssh, "LANG=en_US rpl \"# Authentication:\n\" \"# Authentication:\nAllowUsers root\nAllowUsers " + NEWUSER + "\n\" /etc/ssh/sshd_config", 2);
}
//Allow pub key files
if (sshd_config.contains("#AuthorizedKeysFile")) {
System.out.println("sshd_config: un-commenting #AuthorizedKeysFile");
runCmd(ssh, "LANG=en_US rpl \"#AuthorizedKeysFile\" \"AuthorizedKeysFile\" /etc/ssh/sshd_config", 2);
}
//Allow pubkey auth in sshd_config
if (sshd_config.contains("#PubkeyAuthentication yes")) {
System.out.println("sshd_config: allowing pubkey auth");
runCmd(ssh, "LANG=en_US rpl \"#PubkeyAuthentication yes\" \"PubkeyAuthentication yes\" /etc/ssh/sshd_config", 2);
}
//Secure some other params
System.out.println("sshd_config: securing some arcane params");
runCmd(ssh, "LANG=en_US rpl \"X11Forwarding yes\" \"X11Forwarding no\" /etc/ssh/sshd_config", 2);
runCmd(ssh, "LANG=en_US rpl \"UsePAM yes\" \"UsePAM no\" /etc/ssh/sshd_config", 2);
System.out.println("restarting the sshd service");
runCmd(ssh, "service sshd restart", 2);
System.out.println("disconnecting and reconnecting with the new SSH port");
ssh.disconnect();
ssh.close();
ssh = new SSHClient();
ssh.addHostKeyVerifier(new PromiscuousVerifier());
ssh.connect(SERVERIP, Integer.valueOf(NEWSSHPORT));
ssh.authPassword("root", ROOTPASSWORD);
System.out.println("connection back up!");
//adding a firewall
System.out.println("adding the ufw firewall and denying all traffic except for our ssh connection");
runCmd(ssh, "yes | apt-get install ufw", 2);
runCmd(ssh, "ufw default deny incoming", 2);
runCmd(ssh, "ufw allow " + NEWSSHPORT + "/tcp", 2);
runCmd(ssh, "yes | ufw enable", 2);
//add a new user
System.out.println("adding the expect command");
runCmd(ssh, "yes | apt-get install expect", 2);
System.out.println("adding the expect script to add users");
runCmd(ssh, "echo $'" + getAddUserScript(NEWUSER, NEWUSERPASSWORD) + "' >/scriptAddUser", 2);
System.out.println("running the script to add users");
runCmd(ssh, " /usr/bin/expect /scriptAddUser", 10);
System.out.println("removing the script to add users");
runCmd(ssh, " rm /scriptAddUser", 2);
System.out.println("adding the sudo command if not already there");
runCmd(ssh, "yes | apt-get install sudo", 2);
System.out.println("adding the new user to the sudo group");
runCmd(ssh, "adduser " + NEWUSER + " sudo", 2);
//adding the new user to the list of passwordless sudoers
runCmd(ssh, "echo $'" + NEWUSER + " ALL=(ALL) NOPASSWD:ALL' >/etc/sudoers.d/user", 2);
runCmd(ssh, "service sudo restart", 2);
System.out.println("disconnecting and reconnecting as the new user");
ssh.disconnect();
ssh.close();
ssh = new SSHClient();
ssh.addHostKeyVerifier(new PromiscuousVerifier());
ssh.connect(SERVERIP, Integer.valueOf(NEWSSHPORT));
ssh.authPassword(NEWUSER, NEWUSERPASSWORD);
System.out.println("connected back as " + NEWUSER);
//add a public ssh key and a putty ssh key (one of each: the ssh key to log via scripts, the putty one for windows)
System.out.println("creating an .ssh folder");
runCmd(ssh, "mkdir /home/" + NEWUSER + "/.ssh", 2);
runCmd(ssh, "chmod 700 /home/" + NEWUSER + "/.ssh", 2);
runCmd(ssh, "echo $'" + PUBLICKEY + "\n" + PUBLICPUTTYKEY + "' >/home/" + NEWUSER + "/.ssh/authorized_keys", 2);
runCmd(ssh, "service sshd restart", 2);
ssh.disconnect();
ssh.close();
//reconnecting via private key
System.out.println("reconnecting via new user and private key");
ssh = sshjConnectWithPrivateKey(SERVERIP, Integer.valueOf(NEWSSHPORT), NEWUSER);
System.out.println("adding the expect script to get sudo powers");
runCmd(ssh, "echo $'" + getSudoScript(ROOTPASSWORD) + "' >/home/" + NEWUSER + "/scriptSudo", 2);
System.out.println("running the script to get sudo powers");
runCmd(ssh, "/usr/bin/expect /home/" + NEWUSER + "/scriptSudo", 5);
System.out.println("removing the script to add users");
runCmd(ssh, "rm /home/" + NEWUSER + "/scriptSudo", 2);
//modifying the SSH port to finish securing the access by disabling root access and loggin access
System.out.println("disabling root access and login access");
runCmd(ssh, "sudo LANG=en_US rpl \"PasswordAuthentication yes\" \"PasswordAuthentication no\" /etc/ssh/sshd_config", 2);
runCmd(ssh, "sudo LANG=en_US rpl \"PermitRootLogin yes\" \"PermitRootLogin no\" /etc/ssh/sshd_config", 2);
System.out.println("restarting the sshd service");
runCmd(ssh, "sudo service sshd restart", 2);
System.out.println("The server has now a basic security including:");
System.out.println("- ssh port changed from default 22");
System.out.println("- new user created, root access disabled");
System.out.println("- password login disabled");
System.out.println("- pubkey authentification enabled");
System.out.println("- firewall allowing only for ssh connections on the designated port");
ssh.disconnect();
ssh.close();
}
static SSHClient sshjConnectWithPrivateKey(String ip, Integer port, String user) throws IOException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
SSHClient ssh = new SSHClient();
ssh.addHostKeyVerifier(new PromiscuousVerifier());
File privateKey = new File(PATHTOPRIVATEPUTTYKEY);
KeyProvider keys = ssh.loadKeys(privateKey.getPath());
ssh.connect(ip, port);
ssh.authPublickey(user, keys);
return ssh;
}
public static String runCmd(SSHClient sshClient, String command, int secondsToWaitAfterCmd) throws IOException {
String response = "";
try (Session session = sshClient.startSession()) {
session.allocateDefaultPTY();
final Session.Command cmd = session.exec(command);
response = (IOUtils.readFully(cmd.getInputStream()).toString());
cmd.join(secondsToWaitAfterCmd, TimeUnit.SECONDS);
System.out.println("\n** exit status: " + cmd.getExitStatus());
}
return response;
}
static String getAddUserScript(String userName, String password) {
return "#/usr/bin/expect -f \n"
+ "spawn adduser " + userName + " -shell /bin/bash\n"
+ "\n"
+ "expect \"Enter new UNIX password\"\n"
+ "send \"" + password + "\\r\"\n"
+ "\n"
+ "expect \"Retype new UNIX password\"\n"
+ "send \"" + password + "\\r\"\n"
+ "\n"
+ "expect -ex {Full Name []: }\n"
+ "send \"\\r\"\n"
+ "\n"
+ "expect -ex {Room Number []: }\n"
+ "send \"\\r\"\n"
+ "\n"
+ "expect -ex {Work Phone []: }\n"
+ "send \"\\r\"\n"
+ "\n"
+ "expect -ex {Home Phone []: }\n"
+ "send \"\\r\"\n"
+ "\n"
+ "expect -ex {Other []: }\n"
+ "send \"\\r\"\n"
+ "\n"
+ "expect \"Is the information correct? \\[Y/n\\] \"\n"
+ "send \"Y\\r\""
+ "\n"
+ "expect eof";
}
static String getSudoScript(String password) {
return "#/usr/bin/expect -f \n"
+ "spawn su - \n"
+ "\n"
+ "expect \"Password: \"\n"
+ "send \"" + password + "\\r\"\n"
+ "\n"
+ "expect eof";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment