Last active
February 26, 2021 18:10
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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