├── .gitignore ├── Makefile ├── README.md ├── aws-debian.sh ├── aws-ubuntu.sh ├── gen ├── go.mod ├── go.sum ├── main.go └── template.go └── linode.sh /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO=go 2 | 3 | all: linode.sh aws-debian.sh aws-ubuntu.sh 4 | 5 | bin/gen: gen/template.go gen/main.go 6 | mkdir -p bin && \ 7 | cd gen && \ 8 | $(GO) build -o ../bin/gen 9 | 10 | linode.sh: bin/gen 11 | ./bin/gen \ 12 | --param-type=linode-udf \ 13 | --authorized-keys-path='$$HOME/.ssh/authorized_keys' \ 14 | --copy-root-password \ 15 | --ufw \ 16 | >$@ 17 | 18 | aws-debian.sh: bin/gen 19 | ./bin/gen \ 20 | --remove-user admin \ 21 | --authorized-keys-path=/home/admin/.ssh/authorized_keys \ 22 | --wireguard-output stdout \ 23 | --nat \ 24 | >$@ 25 | 26 | aws-ubuntu.sh: bin/gen 27 | ./bin/gen \ 28 | --remove-user ubuntu \ 29 | --authorized-keys-path=/home/ubuntu/.ssh/authorized_keys \ 30 | --wireguard-output stdout \ 31 | --nat \ 32 | >$@ 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Init Scripts 2 | 3 | Variants of a script for initialzing a VM runnig Debian 11 (Bullseye) or Ubuntu 4 | 22.04. These are meant to be used after the host boots the first time, before 5 | Ansible or a similar tool takes over. 6 | 7 | These scripts will: 8 | 9 | - Create an admin account with sudo access 10 | - Disable root logins 11 | - Disable SSH passwords 12 | - Configure SSH keys to be read from `/etc/ssh/authorized_keys/$USER` 13 | - Configure WireGuard (generate keys, set port number, configure the first peer) 14 | - On Linode, configures `ufw` to block SSH except over WireGuard 15 | 16 | Currently there are published scripts for AWS and Linode. 17 | 18 | This automates the steps from [this guide](https://pboyd.io/posts/securing-a-linux-vm/). 19 | 20 | ### Linode 21 | 22 | The Linode script can be imported as a StackScript (or use [this 23 | one](https://cloud.linode.com/stackscripts/946556)). 24 | 25 | The WireGuard peer information will be written to `/etc/issue`, so you can grab 26 | the public key and port number from the web console. You may need to wait for 27 | the console to refresh before it appears (reboot it if you're impatient). 28 | 29 | This version of the script enables `ufw` to restrict SSH to the WireGuard 30 | interface. If you use Linode's firewall you might prefer to remove that 31 | behavior. 32 | 33 | ### AWS 34 | 35 | To use the AWS scripts, modify the variables at the beginning as supply it as user data. 36 | 37 | The WireGuard peer information will be written to the system log (Actions -> 38 | Monitoring -> Get system log). The config will need the host's public IP filled 39 | in. 40 | 41 | The AWS scripts do not configure `ufw`. You will need to open the WireGuard 42 | port in the AWS firewall (remember it's UDP), and also block public SSH. 43 | -------------------------------------------------------------------------------- /aws-debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | umask 077 5 | 6 | # WG_IP is the WireGuard IP address and subnet. 7 | WG_IP=192.168.50.2/24 8 | 9 | # WG_PEER_PUBLIC_KEY is the public key of the WireGuard peer. 10 | WG_PEER_PUBLIC_KEY=iupfsx9fgp4erSmjmByPEjAoZPdqNat2Zgq1c5qPwig= 11 | 12 | # WG_PEER_ALLOWED_IPS is allowed IPs setting of the WireGuard peer. This should 13 | # normally end with /32. 14 | WG_PEER_ALLOWED_IPS=192.168.50.1/32 15 | 16 | # ADMIN_USER is the name of the user to be created. This is the user you'll log 17 | # in as to manage the host. 18 | ADMIN_USER=user 19 | 20 | apt-get update 21 | apt-get upgrade -y 22 | apt-get install -y acl wireguard 23 | 24 | echo -e "\numask 077" >> /etc/profile 25 | 26 | # Make our admin user. Give it sudo access. 27 | useradd -m -s /bin/bash \ 28 | -G users,sudo \ 29 | -p '' \ 30 | $ADMIN_USER 31 | 32 | passwd -e $ADMIN_USER 33 | 34 | # Since we have a user account with sudo access, there's no reason to log in to root. 35 | passwd -l root 36 | cat >/etc/ssh/sshd_config.d/01-disable-root.conf </etc/ssh/sshd_config.d/02-system-managed-keys.conf </etc/ssh/sshd_config.d/03-no-passwords.conf < /etc/wireguard/public_key 72 | 73 | # Configure WireGuard interface with our parameters and generated values. 74 | cat >/etc/wireguard/wg0.conf <:$WG_PORT 96 | EOF 97 | -------------------------------------------------------------------------------- /aws-ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | umask 077 5 | 6 | # WG_IP is the WireGuard IP address and subnet. 7 | WG_IP=192.168.50.2/24 8 | 9 | # WG_PEER_PUBLIC_KEY is the public key of the WireGuard peer. 10 | WG_PEER_PUBLIC_KEY=iupfsx9fgp4erSmjmByPEjAoZPdqNat2Zgq1c5qPwig= 11 | 12 | # WG_PEER_ALLOWED_IPS is allowed IPs setting of the WireGuard peer. This should 13 | # normally end with /32. 14 | WG_PEER_ALLOWED_IPS=192.168.50.1/32 15 | 16 | # ADMIN_USER is the name of the user to be created. This is the user you'll log 17 | # in as to manage the host. 18 | ADMIN_USER=user 19 | 20 | apt-get update 21 | apt-get upgrade -y 22 | apt-get install -y acl wireguard 23 | 24 | echo -e "\numask 077" >> /etc/profile 25 | 26 | # Make our admin user. Give it sudo access. 27 | useradd -m -s /bin/bash \ 28 | -G users,sudo \ 29 | -p '' \ 30 | $ADMIN_USER 31 | 32 | passwd -e $ADMIN_USER 33 | 34 | # Since we have a user account with sudo access, there's no reason to log in to root. 35 | passwd -l root 36 | cat >/etc/ssh/sshd_config.d/01-disable-root.conf </etc/ssh/sshd_config.d/02-system-managed-keys.conf </etc/ssh/sshd_config.d/03-no-passwords.conf < /etc/wireguard/public_key 72 | 73 | # Configure WireGuard interface with our parameters and generated values. 74 | cat >/etc/wireguard/wg0.conf <:$WG_PORT 96 | EOF 97 | -------------------------------------------------------------------------------- /gen/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pboyd/initscripts/gen 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/spf13/cobra v1.6.1 8 | ) 9 | 10 | require ( 11 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 12 | github.com/spf13/pflag v1.0.5 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /gen/go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 5 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 6 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 7 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 8 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 9 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 10 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /gen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | type Opts struct { 11 | ParamType string 12 | 13 | Username string 14 | CopyRootPassword bool 15 | AuthorizedKeysPath string 16 | RemoveUser string 17 | 18 | WireGuard bool 19 | WireGuardIP string 20 | WireGuardPeerKey string 21 | WireGuardPeerIP string 22 | WireGuardOutput string 23 | NAT bool 24 | 25 | UFW bool 26 | } 27 | 28 | var opts Opts 29 | var rootCmd *cobra.Command 30 | 31 | func init() { 32 | rootCmd = &cobra.Command{ 33 | Use: "gen", 34 | Short: "init script generator", 35 | Run: generate, 36 | } 37 | 38 | flags := rootCmd.Flags() 39 | 40 | flags.StringVar(&opts.ParamType, "param-type", "vars", `how to define params for the generated script (options are "vars" or "linode-udf"`) 41 | 42 | flags.StringVar(&opts.Username, "username", "user", "default username") 43 | flags.BoolVar(&opts.CopyRootPassword, "copy-root-password", false, "if set, copies root's password to the admin user") 44 | flags.StringVar(&opts.AuthorizedKeysPath, "authorized-keys-path", "/home/admin/.ssh/authorized_keys", "file path on the server where the authorized keys will be") 45 | flags.StringVar(&opts.RemoveUser, "remove-user", "", "remove the user with this name") 46 | 47 | flags.BoolVar(&opts.WireGuard, "wireguard", true, "whether or not to configure WireGuard") 48 | flags.StringVar(&opts.WireGuardIP, "wireguard-ip", "192.168.50.2/24", "default IP address to assign to the WireGuard interface") 49 | flags.StringVar(&opts.WireGuardPeerKey, "wireguard-peer-key", "iupfsx9fgp4erSmjmByPEjAoZPdqNat2Zgq1c5qPwig=", "default public key for the WireGuard peer") 50 | flags.StringVar(&opts.WireGuardPeerIP, "wireguard-peer-ip", "192.168.50.1/32", "default IP address for the WireGuard peer") 51 | flags.StringVar(&opts.WireGuardOutput, "wireguard-output", "console", "how to output the generated WireGuard peer config (console or stdout)") 52 | flags.BoolVar(&opts.NAT, "nat", false, "when set, uses a placeholder for the sample peer config instead of trying to get the public IP.") 53 | 54 | flags.BoolVar(&opts.UFW, "ufw", false, "whether or not to configure the UFW firewall") 55 | } 56 | 57 | func main() { 58 | err := rootCmd.Execute() 59 | if err != nil { 60 | fmt.Println(err) 61 | os.Exit(1) 62 | } 63 | } 64 | 65 | func generate(cmd *cobra.Command, args []string) { 66 | if err := tmpl.Execute(os.Stdout, &opts); err != nil { 67 | fmt.Fprintf(os.Stderr, "template error: %v\n", err) 68 | os.Exit(1) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gen/template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "text/template" 4 | 5 | var tmpl = template.Must(template.New("script").Parse(`#!/bin/bash 6 | 7 | set -e 8 | umask 077 9 | 10 | {{ if eq .ParamType "linode-udf" -}} 11 | {{ if .WireGuard -}} 12 | # 13 | # 14 | # 15 | {{ end }} 16 | 17 | # 18 | {{- else -}} 19 | 20 | {{ if .WireGuard -}} 21 | # WG_IP is the WireGuard IP address and subnet. 22 | WG_IP={{ .WireGuardIP }} 23 | 24 | # WG_PEER_PUBLIC_KEY is the public key of the WireGuard peer. 25 | WG_PEER_PUBLIC_KEY={{ .WireGuardPeerKey }} 26 | 27 | # WG_PEER_ALLOWED_IPS is allowed IPs setting of the WireGuard peer. This should 28 | # normally end with /32. 29 | WG_PEER_ALLOWED_IPS={{ .WireGuardPeerIP }} 30 | {{- end }} 31 | 32 | # ADMIN_USER is the name of the user to be created. This is the user you'll log 33 | # in as to manage the host. 34 | ADMIN_USER={{ .Username }} 35 | {{- end }} 36 | 37 | apt-get update 38 | apt-get upgrade -y 39 | apt-get install -y acl{{ if .WireGuard }} wireguard{{ end }}{{ if .UFW }} ufw{{ end }} 40 | 41 | echo -e "\numask 077" >> /etc/profile 42 | 43 | # Make our admin user. Give it{{ if .CopyRootPassword }} root's password and{{ end }} sudo access. 44 | useradd -m -s /bin/bash \ 45 | -G users,sudo \ 46 | -p {{ if .CopyRootPassword }}$(awk -F: '/^root:/ { print $2 }' /etc/shadow){{ else }}''{{ end }} \ 47 | $ADMIN_USER 48 | 49 | {{ if not .CopyRootPassword -}} 50 | passwd -e $ADMIN_USER 51 | {{- end }} 52 | 53 | # Since we have a user account with sudo access, there's no reason to log in to root. 54 | passwd -l root 55 | cat >/etc/ssh/sshd_config.d/01-disable-root.conf </etc/ssh/sshd_config.d/02-system-managed-keys.conf </etc/ssh/sshd_config.d/03-no-passwords.conf < /etc/wireguard/public_key 94 | 95 | # Configure WireGuard interface with our parameters and generated values. 96 | cat >/etc/wireguard/wg0.conf <>/etc/issue <{{ else }}$(hostname -I | awk '{ print $1 }'){{ end }}:$WG_PORT 124 | EOF 125 | {{ end }} 126 | 127 | {{- if .UFW }} 128 | {{- if .WireGuard }} 129 | # Allow anyone to reach the WireGuard port, but require SSH to be on wireguard. 130 | ufw allow in on eth0 to any port $WG_PORT proto udp 131 | ufw allow in on wg0 to any port 22 proto tcp 132 | {{- else }} 133 | ufw allow in on eth0 to any port 22 proto tcp 134 | {{- end }} 135 | ufw enable 136 | {{ end }}`)) 137 | -------------------------------------------------------------------------------- /linode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | umask 077 5 | 6 | # 7 | # 8 | # 9 | 10 | 11 | # 12 | 13 | apt-get update 14 | apt-get upgrade -y 15 | apt-get install -y acl wireguard ufw 16 | 17 | echo -e "\numask 077" >> /etc/profile 18 | 19 | # Make our admin user. Give it root's password and sudo access. 20 | useradd -m -s /bin/bash \ 21 | -G users,sudo \ 22 | -p $(awk -F: '/^root:/ { print $2 }' /etc/shadow) \ 23 | $ADMIN_USER 24 | 25 | 26 | 27 | # Since we have a user account with sudo access, there's no reason to log in to root. 28 | passwd -l root 29 | cat >/etc/ssh/sshd_config.d/01-disable-root.conf </etc/ssh/sshd_config.d/02-system-managed-keys.conf </etc/ssh/sshd_config.d/03-no-passwords.conf < /etc/wireguard/public_key 64 | 65 | # Configure WireGuard interface with our parameters and generated values. 66 | cat >/etc/wireguard/wg0.conf <>/etc/issue <