Created
November 13, 2014 17:47
-
-
Save TomRoche/b254599b6ebeb3ed09c7 to your computer and use it in GitHub Desktop.
a linode StackScript which I was able to save to https://manager.linode.com/stackscripts/edit/10237
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
#!/bin/bash | |
### A fairly minimal, reasonably secure foundation for whatever you might be doing on a debian-based linode. | |
### Mostly this just automates | |
### 1. https://www.linode.com/docs/getting-started/ | |
### 2. https://www.linode.com/docs/security/securing-your-server/ | |
### This StackScript assumes you have previously (on the linode website) created a working linode with known | |
### * IP# (needed below) | |
### * root password | |
### * distro ~= Debian | |
### This StackScript depends/source's the the StackScript Bash Library [ https://www.linode.com/stackscripts/view/1 ] | |
### ---------------------------------------------------------------------- | |
### constants | |
### ---------------------------------------------------------------------- | |
### UDFs: variables you define through the StackScript GUI | |
#<UDF name="user_name" default="" label="The non-root ID with which you intend to SSH to your linode"> | |
#<UDF name="user_password" default="" label="The password you will set on your ID"> | |
#<UDF name="user_public_sshkey" default="" label="The public SSH key for your user (probably from the machine you are on now)"> | |
#<UDF name="my_editor_pkg_names" default="emacs23-nox" label="Name(s) of the package(s) for your editor as a space-delimited string"> | |
#<UDF name="my_hostname" default="happybox" label="Name for your linode"> | |
#<UDF name="my_ipv4" default="123.456.789.abc" label="Your linode's assigned IPv4 IP number"> | |
#<UDF name="my_ipv6" default="1a2b:3c4d::5e6f:f6e5:d4c3:b2a1" label="Your linode's assigned IPv6 IP number"> | |
#<UDF name="my_tz_location" default="America/New_York" label="Desired default timezone location for your linode's users"> | |
### non-UDFs: locally-defined or -derived | |
## just for logging | |
THIS_FN='minimal_secure_debian-based_linode' | |
MESSAGE_PREFIX="${THIS_FN}:" | |
WARNING_PREFIX="${MESSAGE_PREFIX} WARNING:" | |
ERROR_PREFIX="${MESSAGE_PREFIX} ERROR:" | |
## user data | |
# remember to set USER_PASSWORD! | |
if [[ -z "${USER_PASSWORD}" ]] ; then | |
echo -e "${ERROR_PREFIX} USER_PASSWORD not defined, exiting ..." | |
exit 2 | |
fi | |
USER_HOME_DIR="/home/${USER_NAME}" # TODO: get from `usermod` or other | |
USER_SSH_DIR="${USER_HOME_DIR}/.ssh" | |
USER_KEYS_FP="${USER_SSH_DIR}/authorized_keys" | |
## packages to install | |
MINIMAL_PACKAGE_LIST='sudo fail2ban' # always install these | |
MY_PACKAGE_LIST="${MINIMAL_PACKAGE_LIST} less wget ${MY_EDITOR_PKG_NAMES}" | |
## box data | |
MY_FQDN="${MY_HOSTNAME}.linode.com" | |
MY_IPV4_IN_HOSTS_FILE="${MY_IPV4} ${MY_FQDN} ${MY_HOSTNAME}" | |
MY_IPV6_IN_HOSTS_FILE="${MY_IPV6} ${MY_FQDN} ${MY_HOSTNAME}" | |
## firewall | |
# firewall rules. copy/mod from https://www.linode.com/docs/security/securing-your-server/ | |
# TODO: parameterize SSH port# | |
# TODO: fix addition of comments: gotta replace "template '%' with '#' | |
MY_FIREWALL_RULES='*filter | |
%%% Contents (including line above) copy/mod from https://www.linode.com/docs/security/securing-your-server/ | |
%%% Allow all loopback (lo0) traffic and drop all traffic to 127/8 that does not use lo0. | |
-A INPUT -i lo -j ACCEPT | |
-A INPUT -d 127.0.0.0/8 -j REJECT | |
%%% Accept all established inbound connections. | |
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT | |
%%% Allow all outbound traffic. TODO: modify this to only allow certain traffic | |
-A OUTPUT -j ACCEPT | |
%%% Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL). | |
-A INPUT -p tcp --dport 80 -j ACCEPT | |
-A INPUT -p tcp --dport 443 -j ACCEPT | |
%%% Allow SSH connections. | |
%%% note -dport port% must match port% set in sshd_config | |
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT | |
%%% Allow ping | |
-A INPUT -p icmp -j ACCEPT | |
%%% Log iptables denied calls | |
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7 | |
%%% Drop all other inbound: policy=default deny unless explicitly allowed | |
-A INPUT -j DROP | |
-A FORWARD -j DROP | |
COMMIT | |
' # end MY_FIREWALL_RULES | |
MY_FIREWALL_RULES="$(echo -e "${MY_FIREWALL_RULES}" | sed -e 's/%/#/g')" | |
# firewall start script lines, to go in /etc/network/if-pre-up.d/firewall | |
# TODO: fix addition of comments | |
MY_FIREWALL_SCRIPT_LINES='%!/bin/sh | |
/sbin/iptables-restore < /etc/iptables.firewall.rules | |
' | |
MY_FIREWALL_SCRIPT_LINES="$(echo -e "${MY_FIREWALL_SCRIPT_LINES}" | sed -e 's/%/#/g')" | |
### ---------------------------------------------------------------------- | |
### functions | |
### ---------------------------------------------------------------------- | |
## include StackScript Bash Library: see | |
## * https://www.linode.com/docs/platform/stackscripts/ | |
## * https://www.linode.com/stackscripts/view/1 | |
source <ssinclude StackScriptID="1"> | |
## Copy/mod of function=goodstuff from StackScript Bash Library (which this overrides) | |
function goodstuff { | |
if [[ -z "${MY_PACKAGE_LIST}" ]] ; then | |
aptitude -y install ${MINIMAL_PACKAGE_LIST} | |
else | |
aptitude -y install ${MY_PACKAGE_LIST} | |
fi | |
# enable color root prompt | |
sed -i -e 's/^#PS1=/PS1=/' /root/.bashrc # enable the colorful root bash prompt | |
sed -i -e "s/^#alias ll='ls -l'/alias ll='ls -al'/" /root/.bashrc # enable ll list long alias | |
} | |
## Copy/mod of function=user_add_pubkey from StackScript Bash Library (which this overrides). | |
## Adds the user's public key to authorized_keys for the specified user. (This adds `chmod` of SSH dir and keys file ... and improves bash style, IMHO.) | |
## Quote inputs, or the key may not load properly. | |
function user_add_pubkey { | |
# $1 - Required - username | |
# $2 - Required - public key | |
local USERNAME="${1}" | |
local USERPUBKEY="${2}" | |
local USERSSHDIR='' | |
if [[ -z "${USERNAME}" || -z "${USERPUBKEY}" ]] ; then | |
echo -e "${ERROR_PREFIX} must provide a username and a public SSH key" | |
exit 5 | |
elif [[ "${USERNAME}" == 'root' ]] ; then | |
USERSSHDIR='/root/.ssh' | |
else | |
USERSSHDIR="/home/${USERNAME}/.ssh" | |
fi | |
local USERKEYSFILE="${USERSSHDIR}/authorized_keys" | |
mkdir -p "${USERSSHDIR}/" | |
echo -e "${USERPUBKEY}" >> "${USERKEYSFILE}" | |
if [[ "${USERNAME}" != 'root' ]] ; then | |
chown -R "${USERNAME}":"${USERNAME}" "${USERSSHDIR}" | |
chmod 0700 "${USERSSHDIR}" | |
chmod 0600 "${USERKEYSFILE}" | |
fi | |
} | |
## A quick'n'dirty way to "backup" files. | |
## TODO: test if current user lacks sufficient privileges for file to backup. | |
function backup { | |
local BACKUP_FP="${1}" # file to backup | |
local BACKED_UP_FP="${BACKUP_FP}.0" | |
if [[ -z "${BACKUP_FP}" ]] ; then | |
echo -e "${ERROR_PREFIX} must provide path to file to backup" | |
return 1; # use `return` for StackScript Bash Library, `exit` for normal scripts | |
elif [[ ! -r "${BACKUP_FP}" ]] ; then | |
echo -e "${WARNING_PREFIX} nop: cannot read file to backup='${BACKUP_FP}'" | |
elif [[ -r "${BACKED_UP_FP}" ]] ; then | |
echo -e "${WARNING_PREFIX} nop: previous backup='${BACKED_UP_FP}' exists" | |
else | |
cp ${BACKUP_FP} ${BACKED_UP_FP} | |
chmod a-w ${BACKED_UP_FP} | |
fi | |
} | |
### ---------------------------------------------------------------------- | |
### payload | |
### ---------------------------------------------------------------------- | |
## set hostname. uses StackScript Bash Library::system_set_hostname. TODO: test /etc/hostname | |
for CMD in \ | |
"backup /etc/hostname" \ | |
"system_set_hostname '${MY_HOSTNAME}'" \ | |
"cat /etc/hostname" \ | |
; do | |
echo -e "${MESSAGE_PREFIX} ${CMD}" | |
eval "${CMD}" | |
done | |
if [[ -r '/etc/default/dhcpcd' ]] ; then | |
SET_HOSTNAME_LINES="$(fgrep -e 'SET_HOSTNAME' /etc/default/dhcpcd)" | |
if [[ -n "${SET_HOSTNAME_LINES}" ]] ; then | |
echo -e "${WARNING_PREFIX} TODO: comment out line(s) setting token='SET_HOSTNAME' in file='/etc/default/dhcpcd'" | |
fi | |
fi | |
echo # newline | |
## set IP. TODO: test IP#s, /etc/hosts | |
for CMD in \ | |
"backup /etc/hosts" \ | |
"echo >> /etc/hosts" \ | |
"echo -e '# added by ${THIS_FN}' >> /etc/hosts" \ | |
"echo -e '${MY_IPV4_IN_HOSTS_FILE}' >> /etc/hosts" \ | |
"echo -e '${MY_IPV6_IN_HOSTS_FILE}' >> /etc/hosts" \ | |
"cat /etc/hosts" \ | |
; do | |
echo -e "${MESSAGE_PREFIX} ${CMD}" | |
eval "${CMD}" | |
done | |
echo # newline | |
## set timezone as desired: this replaces `dpkg-reconfigure tzdata` | |
for CMD in \ | |
"backup /etc/timezone" \ | |
"echo '${MY_TZ_LOCATION}' > /etc/timezone" \ | |
"dpkg-reconfigure -f noninteractive tzdata" \ | |
; do | |
echo -e "${MESSAGE_PREFIX} ${CMD}" | |
eval "${CMD}" | |
done | |
echo # newline | |
## add new user, after ensuring package=`sudo` is installed. | |
## Note `chpasswd` is the batch-mode/non-interactive `passwd` | |
## TODO: test input strings and paths, (output) results | |
## TODO: replace with code from StackScript Bash Library::??? | |
for CMD in \ | |
"aptitude -y install sudo" \ | |
"adduser ${USER_NAME} --disabled-password --gecos ''" \ | |
"echo '${USER_NAME}:${USER_PASSWORD}' | chpasswd" \ | |
"usermod -aG sudo ${USER_NAME}" \ | |
; do | |
echo -e "${MESSAGE_PREFIX} ${CMD}" | |
eval "${CMD}" | |
done | |
echo # newline | |
if [[ ! -w "${USER_HOME_DIR}" ]] ; then | |
echo -e "${ERROR_PREFIX} cannot write to home dir='${USER_HOME_DIR}' for new user='${USER_NAME}', exiting ..." | |
else | |
# setup user's SSH with this::user_add_pubkey (extends StackScript Bash Library::user_add_pubkey) | |
# disable SSH login as user=root with StackScript Bash Library::ssh_disable_root | |
for CMD in \ | |
"user_add_pubkey '${USER_NAME}' '${USER_PUBLIC_SSHKEY}'" \ | |
"ls -al ${USER_KEYS_FP}" \ | |
"ls -ald ${USER_SSH_DIR}" \ | |
"ssh_disable_root" \ | |
"fgrep -nH -e 'PermitRootLogin' /etc/ssh/sshd_config" \ | |
; do | |
echo -e "${MESSAGE_PREFIX} ${CMD}" | |
eval "${CMD}" | |
done | |
fi | |
echo # newline | |
## setup firewall | |
echo -e "${MESSAGE_PREFIX} initial iptables set=" | |
for CMD in \ | |
"iptables -L" \ | |
; do | |
echo -e "${MESSAGE_PREFIX} ${CMD}" | |
eval "${CMD}" | |
done | |
# "cat /etc/iptables.firewall.rules" \ # superfluous | |
for CMD in \ | |
"backup /etc/iptables.firewall.rules" \ | |
"backup /etc/network/if-pre-up.d/firewall" \ | |
"echo -e '${MY_FIREWALL_RULES}' > /etc/iptables.firewall.rules" \ | |
"iptables-restore < /etc/iptables.firewall.rules" \ | |
"iptables -L" \ | |
"echo -e '${MY_FIREWALL_SCRIPT_LINES}' > /etc/network/if-pre-up.d/firewall" \ | |
"chmod +x /etc/network/if-pre-up.d/firewall" \ | |
"ls -al /etc/network/if-pre-up.d/firewall" \ | |
; do | |
echo -e "${MESSAGE_PREFIX} ${CMD}" | |
eval "${CMD}" | |
done | |
echo # newline | |
## install packages: | |
## `system_update` from StackScript Bash Library | |
## `goodstuff` above overrides implementation in StackScript Bash Library | |
for CMD in \ | |
"system_update" \ | |
"goodstuff" \ | |
; do | |
echo -e "${MESSAGE_PREFIX} ${CMD}" | |
eval "${CMD}" | |
done | |
echo # newline | |
## configure Fail2Ban? no, take defaults. see https://www.linode.com/docs/security/securing-your-server/ | |
## done! | |
restartServices # StackScript Bash Library::restartServices | |
echo -e " | |
${THIS_FN}: complete! Now, logout of this SSH session (as root), and verify that | |
1. you *cannot* now SSH in as user=root | |
2. you *can* now SSH in as user='${USER_NAME}' with key and without challenge | |
3. your 'sudo iptables -L' output | |
3.1. resembles listing @ https://www.linode.com/docs/security/securing-your-server/#creating-a-firewall | |
3.2. ... and your ability to 'sudo' tests that you set your user password correctly. | |
4. your desired packages (esp for your editor) are installed | |
" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment