My Digital Garden

Linode StackScript for basic Ubuntu configuration

Linode StackScript for basic Ubuntu configuration

Overview

This is an example of a Linode StackScript that does basic configuration of a new Ubuntu instance:

  • locks root user
  • creates a non-root user
    • copies SSH keys from root to the non-root user
    • inserts a password for the non-root user
    • enables sudo (with password) for the non-root user
    • configures ufw to block everything except SSH

Using this Stack Script

  1. login to Linode portal
  2. create new stackscript
  3. paste code below
  4. save, note Id number
  5. adjust Terraform model to reference the corr ect Id

Script

#!/bin/bash
# UDF variables
# <UDF name="USER" label="Create a non-root user" example="Using root user directly is not recommended" default=""/>
# <UDF name="USER_PASSWORD" label="Create a non-root user password" example="Example: mo7adL*^*3MD$QJcQYLcKLPrLx" default=""/>
# <UDF name="UPGRADE" label="Upgrade the system automatically ?" oneof="yes,no" default="yes" />
# <UDF name="SSH_PORT" label="Set SSH server port" example="This won't be reflected in your Linode Dashboard" default="22" />
# <UDF name="ROOT_LOCK" label="Lock the root account ?" oneof="yes,no" default="yes" />
# <UDF name="HOSTNAME" label="Host name" default="localhost" />
logfile="/var/log/stackscript.log"
error(){
    for x in "$@"; do
		test -n "$x" && \
			printf "[ERROR] ($(date '+%y-%m-%d %H:%M:%S')) %s\n" "$x" >> $logfile
    done
}
info(){
    for x in "$@"; do
		test -n "$x" && \
			printf "[INFO] ($(date '+%y-%m-%d %H:%M:%S')) %s\n" "$x" >> $logfile
    done
}
log(){
    # log command error info
    local msg
	msg="$(2>&1 eval $1)"
    [ $? -ne 0 ] && \
        error "$msg" "$2" || \
            info "$msg" "$3"
}
## User creation ##
user_create() {
    # user_create user [password]
    local ret=0
    [ -z "$USER_PASSWORD" ] && \
		USER_PASSWORD=$(awk -F: '$1 ~ /^root$/ { print $2 }' /etc/shadow) \
		|| USER_PASSWORD=$(openssl passwd -6 $USER_PASSWORD)
    ret=$?
    
    useradd -mG sudo \
        -s /bin/bash \
        -p $USER_PASSWORD \
        $USER
    ret=$((ret+$?))
    return $ret
}
ssh_config(){
    # ssh_config ...
    local ret=0
    local sedopts="-i -E /etc/ssh/sshd_config -e 's/.*Port 22/Port $SSH_PORT/' \
                    -e 's/.*(PermitEmptyPasswords) .+/\1 no/' \
                    -e 's/.*(X11Forwarding) .+/\1 no/' \
                    -e 's/.*(ClientAliveInterval) .+/\1 300/' \
                    -e 's/.*(ClientAliveCountMax) .+/\1 2/' \
                    -e 's/.*(PubkeyAuthentication) .+/\1 yes/'"
    if [ -d /root/.ssh ]; then
        
        if [ "$USER" ]; then
            sedopts="$sedopts -e 's/.*(PermitRootLogin) .+/\1 no/'"
            cp -r /root/.ssh /home/$USER && \
                chown -R $USER:$USER /home/$USER/.ssh && \
                chmod 700 /home/$USER/.ssh
                ret=$?
        else
            sedopts="$sedopts -e 's/.*(PermitRootLogin) .+/\1 yes/'"
        fi
        
        sedopts="$sedopts -e 's/.*(PasswordAuthentication) .+/\1 no/'"
    else
        sedopts="$sedopts -e 's/.*(PasswordAuthentication) .+/\1 yes/'"
    fi
        
    eval sed $sedopts
    ret=$((ret+$?))
    systemctl restart ssh
    ret=$((ret+$?))
    return $ret
}
debian_upgrade(){
	export DEBIAN_FRONTEND="noninteractive"
	>/dev/null 2>&1 apt update -qq && \
		>/dev/null 2>&1 apt upgrade -qqy
}
log "user_create" \
    "$USER creation failed." "$USER creation successful."
log "ssh_config" \
    "SSH configuration failed." "SSH configuration successful."
[ "$ROOT_LOCK" = "yes" ] && {
    log "passwd -l root" \
        "root lock failed." "root locked successfully."
}
[ "$UPGRADE" = "yes" ] && {
    log "debian_upgrade" \
        "System upgrade failed." "System upgrade completed successfully."
}
hostnamectl set-hostname $HOSTNAME
ufw default deny incoming
ufw default allow outgoing
ufw allow $SSH_PORT/tcp
ufw enable

Adapted from this script (c) Debdut Chakraborty for Linux Handbook 2021, licensed under GNU General Public License v3

See also