Skip to content

Instantly share code, notes, and snippets.

@TomRoche
Created November 13, 2014 17:47
Show Gist options
  • Save TomRoche/b254599b6ebeb3ed09c7 to your computer and use it in GitHub Desktop.
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
#!/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