├── .gitignore ├── README.md ├── ansible.cfg ├── bootstrap.yml ├── collectd.yml ├── group_vars └── server.yml.sample ├── hosts.sample ├── reboot.yml ├── roles ├── bootstrap │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── apt.yml │ │ ├── hostname.yml │ │ ├── locale.yml │ │ ├── main.yml │ │ ├── packages.yml │ │ └── secure.yml │ └── templates │ │ ├── apt │ │ └── 10periodic │ │ └── locale │ │ └── timezone ├── collectd │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── nginx.conf │ │ └── supervisor.conf ├── nginx │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── reboot │ └── tasks │ │ └── main.yml └── user │ └── tasks │ └── main.yml └── user.yml /.gitignore: -------------------------------------------------------------------------------- 1 | group_vars/server.yml 2 | hosts 3 | *.retry 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boostrapping and securing an Ubuntu server 2 | 3 | This repository contains [Ansible](http://ansible.com) scripts for bootstrapping and securing an Ubuntu server. 4 | Scripts have been tested on Ubuntu 14.04 hosted on [RunAbove](http://www.runabove.com) and [DigitalOcean](http://www.digitalocean.com). 5 | 6 | The included tasks are following: 7 | 8 | * Update and upgrade Ubuntu packages via apt-get 9 | * Configure locale 10 | * Install ntp to synchronize time 11 | * Install vim and mc (my personal preference) 12 | * Install fail2ban to block ssh brute-force attempts 13 | * Delete root password 14 | * Lock down sudo 15 | * Lock down ssh to prevent root and password login 16 | * Setup the ufw firewall 17 | * Configure unattended security upgrades 18 | * Install collectd deamon and collect-web front-end client (optionally) 19 | * Create users (optionally) 20 | 21 | ## Ansible 22 | 23 | First of all, install the latest version of Ansible, in Ubuntu: 24 | 25 | ``` 26 | $ sudo apt-get install software-properties-common 27 | $ sudo apt-add-repository ppa:ansible/ansible 28 | $ sudo apt-get update 29 | $ sudo apt-get install ansible 30 | ``` 31 | 32 | ## Clone scripts 33 | 34 | Next, clone this repository: 35 | 36 | ``` 37 | $ git clone https://github.com/zenzire/ansible-bootstrap-ubuntu.git 38 | ``` 39 | 40 | ## Configuration files 41 | 42 | Copy sample configuration files: 43 | 44 | ``` 45 | $ cp hosts.sample hosts 46 | $ cp group_vars/server.yml.sample group_vars/server.yml 47 | ``` 48 | 49 | Edit configuration files (hosts and group_vars/server.yml) with your own configuration. 50 | 51 | ## Prerequisites for RunAbove hosting 52 | 53 | Set password for admin user and add this user to sudoers group. 54 | 55 | ``` 56 | $ ansible-playbook user.yml 57 | 58 | Enter username: admin 59 | Enter password: 60 | confirm Enter password: 61 | Enter id_rsa.pub path [~/.ssh/id_rsa.pub]: 62 | Add user to sudoers group (y/n) [n]: y 63 | ``` 64 | 65 | ## Prerequisites for DigitalOcean hosting 66 | 67 | Create admin user and add this user to sudoers group. 68 | 69 | ``` 70 | $ ansible-playbook user.yml --user root 71 | 72 | Enter username: admin 73 | Enter password: 74 | confirm Enter password: 75 | Enter id_rsa.pub path [~/.ssh/id_rsa.pub]: 76 | Add user to sudoers group (y/n) [n]: y 77 | ``` 78 | 79 | ## Script execution 80 | 81 | Finally, execute bootstrap Ansible task for admin user: 82 | 83 | ``` 84 | $ ansible-playbook bootstrap.yml --ask-sudo 85 | sudo password: 86 | ``` 87 | 88 | ## Reboot 89 | 90 | After successfully bootstrapping and securing your server, reboot server for kernel updates. 91 | 92 | ``` 93 | $ ansible-playbook reboot.yml --ask-sudo 94 | sudo password: 95 | Are you sure you want to reboot server (yes/no)? [no]: yes 96 | ``` 97 | 98 | ## Collectd 99 | 100 | Also you can install [collectd](https://collectd.org/), deamon which collects system performance statistics 101 | periodically and [collectd-web](https://github.com/httpdss/collectd-web), web-based front-end for data 102 | collected by collectd. 103 | 104 | ``` 105 | $ ansible-playbook collectd.yml --ask-sudo 106 | ``` 107 | 108 | ## Users 109 | 110 | You can add new user or update the existing one using the following script: 111 | 112 | ``` 113 | $ ansible-playbook user.yml --ask-sudo 114 | ``` 115 | 116 | 117 | ## License 118 | 119 | Released under the MIT License, Copyright (c) 2015 - Marcin Mierzejewski 120 | 121 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | hostfile = hosts 3 | remote_user = rogerz 4 | -------------------------------------------------------------------------------- /bootstrap.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Bootstrap Ubuntu servers 3 | hosts: server 4 | become: yes 5 | roles: 6 | - bootstrap 7 | -------------------------------------------------------------------------------- /collectd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Bootstrap Ubuntu servers 3 | hosts: server 4 | become: yes 5 | roles: 6 | - nginx 7 | - collectd 8 | -------------------------------------------------------------------------------- /group_vars/server.yml.sample: -------------------------------------------------------------------------------- 1 | --- 2 | hostname: 'mydomain.com' 3 | collectd_server_name: 'collecd-web.mydomain.com' 4 | locale: 'en_US.UTF-8' 5 | language: 'en_US:en' 6 | timezone: 'Europe/Warsaw' 7 | ntp_server: 'pool.ntp.org' 8 | 9 | -------------------------------------------------------------------------------- /hosts.sample: -------------------------------------------------------------------------------- 1 | [server] 2 | 127.0.0.1 3 | -------------------------------------------------------------------------------- /reboot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Reboot server 3 | hosts: server 4 | become: yes 5 | roles: 6 | - reboot 7 | 8 | vars_prompt: 9 | - name: "reboot" 10 | prompt: "Are you sure you want to reboot server (yes/no)?" 11 | private: no 12 | default: "no" 13 | -------------------------------------------------------------------------------- /roles/bootstrap/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart ssh 3 | action: service name=ssh state=restarted 4 | 5 | - name: update tzdata 6 | command: /usr/sbin/dpkg-reconfigure --frontend noninteractive tzdata 7 | 8 | -------------------------------------------------------------------------------- /roles/bootstrap/tasks/apt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update APT package cache 3 | action: apt update_cache=yes 4 | 5 | - name: Upgrade APT to the lastest packages 6 | action: apt upgrade=safe 7 | 8 | - name: Install unattended-upgrades 9 | action: apt pkg=unattended-upgrades state=present 10 | 11 | - name: Adjust APT update intervals 12 | template: src=apt/10periodic dest=/etc/apt/apt.conf.d/10periodic 13 | 14 | - name: Make sure unattended-upgrades only installs from security 15 | action: lineinfile dest=/etc/apt/apt.conf.d/50unattended-upgrades regexp="distro_codename\}\-updates" state=absent 16 | 17 | -------------------------------------------------------------------------------- /roles/bootstrap/tasks/hostname.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set hostname 3 | action: shell hostnamectl set-hostname {{ hostname }} 4 | 5 | -------------------------------------------------------------------------------- /roles/bootstrap/tasks/locale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Generate locale 3 | command: /usr/sbin/locale-gen {{ locale }} 4 | 5 | - name: Set locale 6 | command: /usr/sbin/update-locale LANG={{ locale }} LC_ALL={{ locale }} LANGUAGE={{ language }} 7 | 8 | - name: Set /etc/localtime 9 | command: /bin/cp /usr/share/zoneinfo/{{ timezone }} /etc/localtime 10 | 11 | - name: Set timezone (/etc/timezone) 12 | template: src=locale/timezone dest=/etc/timezone 13 | notify: update tzdata 14 | 15 | - name: Install ntp packages 16 | action: apt pkg=ntp state=installed 17 | -------------------------------------------------------------------------------- /roles/bootstrap/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: hostname.yml 3 | - include: apt.yml 4 | - include: secure.yml 5 | - include: packages.yml 6 | - include: locale.yml 7 | 8 | -------------------------------------------------------------------------------- /roles/bootstrap/tasks/packages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install packages 3 | action: apt pkg={{ item }} state=installed 4 | with_items: 5 | - vim 6 | - mc 7 | 8 | -------------------------------------------------------------------------------- /roles/bootstrap/tasks/secure.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install fail2ban 3 | action: apt pkg=fail2ban state=installed 4 | 5 | - name: Disallow password authentication 6 | action: lineinfile dest=/etc/ssh/sshd_config regexp="^PasswordAuthentication" line="PasswordAuthentication no" state=present 7 | notify: restart ssh 8 | 9 | - name: Disallow root SSH access 10 | action: lineinfile dest=/etc/ssh/sshd_config regexp="^PermitRootLogin" line="PermitRootLogin no" state=present 11 | notify: restart ssh 12 | 13 | - name: Delete /etc/sudoers.d/ files 14 | action: file path=/etc/sudoers.d/* state=absent 15 | 16 | - name: Install packages 17 | action: apt pkg=ufw state=installed 18 | 19 | - name: Setup ufw 22/tcp 20 | action: shell ufw allow 22/tcp 21 | 22 | - name: Setup ufw 80/tcp 23 | action: shell ufw allow 80/tcp 24 | 25 | - name: Setup ufw 443/tcp 26 | action: shell ufw allow 443/tcp 27 | 28 | - name: Enable ufw 29 | action: shell echo 'y' | ufw enable 30 | 31 | - name: Delete root password 32 | action: shell passwd -d root 33 | -------------------------------------------------------------------------------- /roles/bootstrap/templates/apt/10periodic: -------------------------------------------------------------------------------- 1 | APT::Periodic::Update-Package-Lists "1"; 2 | APT::Periodic::Download-Upgradeable-Packages "1"; 3 | APT::Periodic::AutocleanInterval "7"; 4 | APT::Periodic::Unattended-Upgrade "1"; 5 | -------------------------------------------------------------------------------- /roles/bootstrap/templates/locale/timezone: -------------------------------------------------------------------------------- 1 | {{ timezone }} 2 | -------------------------------------------------------------------------------- /roles/collectd/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart supervisor 3 | action: service name=supervisor state=restarted 4 | -------------------------------------------------------------------------------- /roles/collectd/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add collectd user 3 | action: user name=collectd shell=/usr/sbin/nologin 4 | 5 | - name: Change user directory permissions to 750 6 | file: path=/home/collectd state=directory mode=750 7 | 8 | - name: Install collectd and dependencies 9 | action: apt pkg={{ item }} state=installed 10 | with_items: 11 | - git 12 | - collectd 13 | - librrds-perl 14 | - libjson-perl 15 | - libhtml-parser-perl 16 | 17 | - name: Clone collectd-web 18 | git: repo=https://github.com/httpdss/collectd-web.git dest=/home/collectd/collectd-web 19 | 20 | - name: Change ownership of collectd-web directory 21 | file: path=/home/collectd/collectd-web state=directory owner=collectd group=collectd 22 | 23 | - name: Copy nginx configuration 24 | template: src=nginx.conf dest=/etc/nginx/sites-enabled/collectd.conf 25 | notify: restart nginx 26 | 27 | - name: Install supervisor 28 | action: apt pkg=supervisor state=present 29 | 30 | - name: Copy supervisor configuration 31 | template: src=supervisor.conf dest=/etc/supervisor/conf.d/collectd.conf 32 | notify: restart supervisor 33 | 34 | 35 | -------------------------------------------------------------------------------- /roles/collectd/templates/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name {{ collectd_server_name }}; 4 | 5 | location / { 6 | proxy_pass http://127.0.0.1:8093; 7 | proxy_set_header Host $host; 8 | proxy_set_header X-Real-IP $remote_addr; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /roles/collectd/templates/supervisor.conf: -------------------------------------------------------------------------------- 1 | [program:collectd] 2 | command=/home/collectd/collectd-web/runserver.py 127.0.0.1 8093 3 | directory=/home/collectd/collectd-web 4 | user=collectd 5 | numprocs=1 6 | autostart=true 7 | autorestart=true 8 | startsecs=10 9 | -------------------------------------------------------------------------------- /roles/nginx/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart nginx 3 | action: service name=nginx state=restarted 4 | -------------------------------------------------------------------------------- /roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install nginx 3 | action: apt pkg=nginx state=present 4 | 5 | - name: Remove default nginx configuration 6 | action: file path=/etc/nginx/sites-enabled/default state=absent 7 | notify: restart nginx 8 | -------------------------------------------------------------------------------- /roles/reboot/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart machine 3 | command: shutdown -r now "Ansible updates triggered" 4 | async: 0 5 | poll: 0 6 | ignore_errors: true 7 | when: reboot == "yes" 8 | 9 | - name: waiting for server to come back 10 | sudo: False 11 | local_action: wait_for host="{{ inventory_hostname }}" port=22 delay=1 timeout=300 12 | when: reboot == "yes" 13 | -------------------------------------------------------------------------------- /roles/user/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add or update user and set password 3 | action: user name={{ username }} password={{ password }} shell=/bin/bash 4 | 5 | - name: Change user directory permissions to 750 6 | file: path=/home/{{ username }} state=directory mode=750 7 | 8 | - name: Add or udpate authorized key 9 | action: authorized_key user={{ username }} key="{{ lookup('file', id_rsa_path) }}" 10 | when: id_rsa_path is defined 11 | 12 | - name: Add user to sudoers 13 | action: lineinfile dest=/etc/sudoers regexp="{{ username }} ALL" line="{{ username }} ALL=(ALL) ALL" state=present 14 | when: sudoers == 'y' 15 | 16 | - name: Remove user to sudoers 17 | action: lineinfile dest=/etc/sudoers regexp="{{ username }} ALL" line="{{ username }} ALL=(ALL) ALL" state=absent 18 | when: sudoers == 'n' 19 | 20 | -------------------------------------------------------------------------------- /user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add or update the existing user on Ubuntu servers 3 | hosts: server 4 | become: yes 5 | roles: 6 | - user 7 | 8 | vars_prompt: 9 | - name: "username" 10 | prompt: "Enter username" 11 | private: no 12 | 13 | - name: "password" 14 | prompt: "Enter password" 15 | encrypt: "md5_crypt" 16 | salt_size: 7 17 | confirm: yes 18 | 19 | - name: "id_rsa_path" 20 | prompt: "Enter id_rsa.pub path" 21 | default: "~/.ssh/id_rsa.pub" 22 | private: no 23 | 24 | - name: "sudoers" 25 | prompt: "Add user to sudoers group (y/n)" 26 | default: 'n' 27 | private: no 28 | --------------------------------------------------------------------------------