Created
November 13, 2014 17:50
-
-
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"
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
#!/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