Skip to content

Instantly share code, notes, and snippets.

@TomRoche
Created November 13, 2014 17:50
Show Gist options
  • Save TomRoche/8967b2bf938941edaf0e to your computer and use it in GitHub Desktop.
Save TomRoche/8967b2bf938941edaf0e to your computer and use it in GitHub Desktop.
this StackScript cannot be saved via https://manager.linode.com/stackscripts/edit/10237 : draws error="Duplicate name found. A UDF's name must be unique"
#!/usr/bin/env 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: constants you define through the StackScript GUI
### ----------------------------------------------------------------------
#<UDF name="linode_user_name" default="" label="The non-root ID with which you intend to SSH to your linode">
#<UDF name="linode_user_password" default="" label="The password you will set on your ID">
#<UDF name="linode_user_public_sshkey" default="" label="The public SSH key for your user (probably from the machine you are on now)">
#<UDF name="linode_hostname" default="happybox" label="Name for your linode">
#<UDF name="linode_ipv4" default="123.456.789.abc" label="Your linode's assigned IPv4 IP number">
#<UDF name="linode_ipv6" default="1a2b:3c4d::5e6f:f6e5:d4c3:b2a1" label="Your linode's assigned IPv6 IP number">
#<UDF name="linode__tz_location" default="America/New_York" label="Desired default timezone location for your linode's users">
#<UDF name="minimal_package_list" default="sudo fail2ban less wget" label="Name(s) of non-editor package(s) to install on your linode, as a space-delimited string, as defined by https://www.linode.com/docs/security/securing-your-server/">
#<UDF name="linode_editor_pkg_list" default="emacs23-nox" label="Name(s) of the package(s) to install for your editor(s), as a space-delimited string">
### ----------------------------------------------------------------------
### non-UDFs: locally-defined or -derived
### ----------------------------------------------------------------------
### just for logging
THIS_FN='minimal_secure_debian-based_linode' # who we are
MESSAGE_PREFIX="${THIS_FN}:"
WARNING_PREFIX="${MESSAGE_PREFIX} WARNING:"
ERROR_PREFIX="${MESSAGE_PREFIX} ERROR:"
### private properties (corresponding to private.properties in ../scripts/)
if [[ -z "${LINODE_USER_PASSWORD}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_USER_PASSWORD not defined, exiting ..."
exit 4
elif [[ -z "${LINODE_USER_NAME}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_USER_NAME not defined, exiting ..."
exit 5
elif [[ -z "${LINODE_USER_PUBLIC_SSHKEY}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_USER_PUBLIC_SSHKEY not defined, exiting ..."
exit 6
elif [[ -z "${LINODE_HOSTNAME}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_HOSTNAME not defined, exiting ..."
exit 7
elif [[ -z "${LINODE_IPV4}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_IPV4 not defined, exiting ..."
exit 8
elif [[ -z "${LINODE_IPV6}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_IPV6 not defined, exiting ..."
exit 9
# can `source private.properties`, but can't compose UDFs
# elif [[ -z "${LINODE_FQDN}" ]] ; then
# echo -e "${ERROR_PREFIX} LINODE_FQDN not defined, exiting ..."
# exit 10
elif [[ -z "${LINODE_TZ_LOCATION}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_TZ_LOCATION not defined, exiting ..."
exit 11
fi
## derived from UDFs
LINODE_FQDN="${LINODE_HOSTNAME}.linode.com"
LINODE_USER_HOME_DIR="/home/${LINODE_USER_NAME}" # TODO: get from `usermod` or other
LINODE_USER_SSH_DIR="${LINODE_USER_HOME_DIR}/.ssh"
LINODE_USER_KEYS_FP="${LINODE_USER_SSH_DIR}/authorized_keys"
LINODE_IPV4_IN_HOSTS_FILE="${LINODE_IPV4} ${LINODE_FQDN} ${LINODE_HOSTNAME}"
LINODE_IPV6_IN_HOSTS_FILE="${LINODE_IPV6} ${LINODE_FQDN} ${LINODE_HOSTNAME}"
### public properties (corresponding to public.properties in ../scripts/
## packages to install
LINODE_PACKAGE_LIST="${MINIMAL_PACKAGE_LIST} ${LINODE_EDITOR_PKG_LIST}"
## 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 '#'
LINODE_FIREWALL_RULES_FP='/etc/iptables.firewall.rules' # path to rules file
LINODE_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 LINODE_FIREWALL_RULES
LINODE_FIREWALL_RULES="$(echo -e "${LINODE_FIREWALL_RULES}" | sed -e 's/%/#/g')"
# firewall start script lines
# TODO: fix addition of comments
LINODE_FIREWALL_SCRIPT_FP='/etc/network/if-pre-up.d/firewall'
LINODE_FIREWALL_SCRIPT_LINES="%!/bin/sh
/sbin/iptables-restore < ${LINODE_FIREWALL_RULES_FP}
"
LINODE_FIREWALL_SCRIPT_LINES="$(echo -e "${LINODE_FIREWALL_SCRIPT_LINES}" | sed -e 's/%/#/g')"
if [[ -z "${LINODE_PACKAGE_LIST}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_PACKAGE_LIST not defined, exiting ..."
exit 14
elif [[ -z "${LINODE_FIREWALL_RULES}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_FIREWALL_RULES not defined, exiting ..."
exit 15
elif [[ -z "${LINODE_FIREWALL_SCRIPT_LINES}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_FIREWALL_SCRIPT_LINES not defined, exiting ..."
exit 16
elif [[ -z "${LINODE_FIREWALL_RULES_FP}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_FIREWALL_RULES_FP not defined, exiting ..."
exit 17
elif [[ -z "${LINODE_FIREWALL_SCRIPT_FP}" ]] ; then
echo -e "${ERROR_PREFIX} LINODE_FIREWALL_SCRIPT_FP not defined, exiting ..."
exit 18
elif [[ -z "${STACKSCRIPT_BASH_LIBRARY_FN}" ]] ; then
echo -e "${ERROR_PREFIX} STACKSCRIPT_BASH_LIBRARY_FN not defined, exiting ..."
exit 19
elif [[ -z "${MY_BASH_LIBRARY_FN}" ]] ; then
echo -e "${ERROR_PREFIX} MY_BASH_LIBRARY_FN not defined, exiting ..."
exit 20
# no: this will be created
# elif [[ ! -r "${LINODE_FIREWALL_RULES_FP}" ]] ; then
# echo -e "${ERROR_PREFIX} cannot read firewall rules file='${LINODE_FIREWALL_RULES_FP}', exiting ..."
# exit 21
# this will also be created
# elif [[ ! -r "${LINODE_FIREWALL_SCRIPT_FP}" ]] ; then
# echo -e "${ERROR_PREFIX} cannot read firewall script file='${LINODE_FIREWALL_SCRIPT_FP}', exiting ..."
# exit 22
fi
### ----------------------------------------------------------------------
### functions
### ----------------------------------------------------------------------
## include StackScript Bash Library: see
## * https://www.linode.com/docs/platform/stackscripts/
## * https://www.linode.com/stackscripts/view/1
source <ssinclude StackScriptID="1">
### TODO: provide following from another StackScript (à la ../scripts/my_linode_bash_library.sh)
## Copy/mod of function=goodstuff from StackScript Bash Library (which this overrides)
function goodstuff {
if [[ -z "${LINODE_PACKAGE_LIST}" ]] ; then
aptitude -y install ${MINIMAL_PACKAGE_LIST}
else
aptitude -y install ${LINODE_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 '${LINODE_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 '${LINODE_IPV4_IN_HOSTS_FILE}' >> /etc/hosts" \
"echo -e '${LINODE_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 '${LINODE_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 ${LINODE_USER_NAME} --disabled-password --gecos ''" \
"echo '${LINODE_USER_NAME}:${LINODE_USER_PASSWORD}' | chpasswd" \
"usermod -aG sudo ${LINODE_USER_NAME}" \
; do
echo -e "${MESSAGE_PREFIX} ${CMD}"
eval "${CMD}"
done
echo # newline
if [[ ! -w "${LINODE_USER_HOME_DIR}" ]] ; then
echo -e "${ERROR_PREFIX} cannot write to home dir='${LINODE_USER_HOME_DIR}' for new user='${LINODE_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 '${LINODE_USER_NAME}' '${LINODE_USER_PUBLIC_SSHKEY}'" \
"ls -al ${LINODE_USER_KEYS_FP}" \
"ls -ald ${LINODE_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 ${LINODE_FIREWALL_RULES_FP}" \ # superfluous
for CMD in \
"backup ${LINODE_FIREWALL_RULES_FP}" \
"backup ${LINODE_FIREWALL_SCRIPT_FP}" \
"echo -e '${LINODE_FIREWALL_RULES}' > ${LINODE_FIREWALL_RULES_FP}" \
"iptables-restore < ${LINODE_FIREWALL_RULES_FP}" \
"iptables -L" \
"echo -e '${LINODE_FIREWALL_SCRIPT_LINES}' > ${LINODE_FIREWALL_SCRIPT_FP}" \
"chmod +x ${LINODE_FIREWALL_SCRIPT_FP}" \
"ls -al ${LINODE_FIREWALL_SCRIPT_FP}" \
; 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, return to your client, and verify that
1. you *cannot* now SSH into your linode as user=root
2. you *can* now SSH into your linode as user='${LINODE_USER_NAME}', with key authentication and without password challenge
3. your desired packages (e.g., for your editor) are installed on your linode
4. your 'sudo iptables -L' output
4.1. resembles listing @ https://www.linode.com/docs/security/securing-your-server/#creating-a-firewall
4.2. ... and your ability to 'sudo' tests that you set your user password correctly.
"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment