├── .gitignore ├── README.md ├── handlers └── site.yml ├── localhost ├── newdroplet.yml ├── requirements.txt ├── tasks ├── droplet.yml ├── security.yml ├── software.yml └── users.yml └── vars.yml /.gitignore: -------------------------------------------------------------------------------- 1 | credentials/* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an [Ansible](http://ansible.com) playbook to bootstrap a brand new DigitalOcean virtual server. 2 | 3 | Tested With Ubuntu LTS 12.04 32bit 4 | 5 | Please *be aware* this playbook is *not* [idempotent](http://docs.ansible.com/glossary.html#idempotency) at least the part which in charge of droplet creation. So if you run it twice you will get two droplets. 6 | 7 | # Setup 8 | Just run `sudo pip install -r requirements.txt` and following packages will be installed 9 | ``` 10 | ansible 11 | dopy python module 12 | passlib python module 13 | ``` 14 | Ansible 1.6 or higher is required for this playbook, `ufw` and `debconf` modules are not available in earlier versions. 15 | 16 | # Initial configuration 17 | You should know you `Client ID` and `API key` from you Digital Ocean [account](https://cloud.digitalocean.com/droplets) 18 | 19 | All configurable variables are in `vars.yml` file. Mandatory parameters are below 20 | * DigitalOcean `Client ID` 21 | * DigitalOcean `API key` 22 | * ssh public key file 23 | 24 | # Run! 25 | 26 | ``` 27 | ansible-playbook -i localhost newdroplet.yml 28 | ``` 29 | Be aware playbook will change root password you received by email. SSH login for the root user will be disabled as well 30 | so use admin user account and `sudo` instead. All new passwords will be saved into ./credentials/$hostname/$username files. 31 | 32 | # Playbook is actually doing steps 33 | 34 | ## Droplet 35 | * deploy ssh keys 36 | * create droplet 37 | 38 | ## Users and groups 39 | * configure remote access 40 | * change default root password - always a good idea, default root password received by email in clear text 41 | * create admin user profile - it would be a main profile for all operations 42 | 43 | ## Security settings 44 | * configure sudo -- allow sudo for certain groups of users (administrators) 45 | * configure ssh 46 | * create new server ssh keys -- Despite the fact that [this](https://www.digitalocean.com/company/blog/avoid-duplicate-ssh-host-keys/) is not an issue anymore it seems like good practice for me. The default keys have been created out of my control. 47 | * restrict sshd settings 48 | * Restrict ssh login for certain group of users (`AllowGroup`) 49 | * Disable root login via ssh (`RootLogin no`) 50 | * Password guessing protection (`MaxAuthTries`, `LoginGraceTime`, `MaxSessions`, `MaxStartups`) 51 | * enable `ufw` (firewall) 52 | 53 | ## Software 54 | 55 | * install updates -- it goes without saying 56 | * configure mail forwarding for admin user -- to keep in touch with my servers 57 | * fail2ban -- prevent password guessing 58 | * ntpd is not about security however it's a vps so this thing is essential 59 | 60 | # Next steps to enhance security (not implemented) 61 | * Restrict ssh access by IP/network -- this is really good idea for production environment 62 | * Change default ssh port or enable port knocking -- mitigate massive port scan attempts 63 | * disable ipv6 64 | * There are some options /etc/sysctl.conf (coredums and etc) 65 | * enable mount options for /tmp (noexec, nodev) 66 | -------------------------------------------------------------------------------- /handlers/site.yml: -------------------------------------------------------------------------------- 1 | - name: restart sshd 2 | service: name=ssh state=restarted 3 | 4 | - name: restart postfix 5 | service: name=postfix state=restarted 6 | 7 | - name: update aliases 8 | command: newaliases 9 | -------------------------------------------------------------------------------- /localhost: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /newdroplet.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | serial: 1 5 | gather_facts: no 6 | vars_files: 7 | - vars.yml 8 | tasks: 9 | - include: tasks/droplet.yml 10 | 11 | - hosts: newdroplets 12 | user: root 13 | vars_files: 14 | - vars.yml 15 | tasks: 16 | - include: tasks/users.yml 17 | - include: tasks/security.yml 18 | - include: tasks/software.yml 19 | 20 | handlers: 21 | - include: handlers/site.yml 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ansible 2 | dopy 3 | passlib 4 | -------------------------------------------------------------------------------- /tasks/droplet.yml: -------------------------------------------------------------------------------- 1 | - name: Deploy public key 2 | digital_ocean: > 3 | state=present 4 | command=ssh 5 | name=my_ssh_key 6 | client_id="{{ client_id }}" 7 | api_key="{{ api_key }}" 8 | ssh_pub_key="{{ my_pub_key }}" 9 | register: do_droplet 10 | 11 | - name: Create Digital Ocean droplet 512Mb/Ubuntu12.04 LTS/Amsterdam 12 | digital_ocean: > 13 | state=present 14 | command=droplet 15 | name="{{ do_hostname }}" 16 | client_id="{{ client_id }}" 17 | api_key="{{ api_key }}" 18 | size_id=66 19 | ssh_key_ids="{{ do_droplet.ssh_key.id }}" 20 | region_id=5 21 | image_id=1505527 22 | wait_timeout=600 23 | register: do_droplet 24 | 25 | - debug: msg="ID is {{ do_droplet.droplet.id }}" 26 | - debug: msg="IP is {{ do_droplet.droplet.ip_address }}" 27 | 28 | - name: Add new droplet to inventory 29 | add_host: > 30 | name="{{ do_droplet.droplet.ip_address }}" 31 | groups=newdroplets 32 | 33 | -------------------------------------------------------------------------------- /tasks/security.yml: -------------------------------------------------------------------------------- 1 | - name: Ensure sudoers.d is enabled 2 | lineinfile: > 3 | dest=/etc/sudoers 4 | regexp='^#includedir /etc/sudoers.d' 5 | line='#includedir /etc/sudoers.d' 6 | state=present backup=yes 7 | 8 | - name: Set up password-less sudo for admin users 9 | copy: > 10 | content='%{{ sudo_group }} ALL=(ALL) NOPASSWD:ALL' 11 | dest=/etc/sudoers.d/admin 12 | owner=root group=root mode=0440 13 | 14 | - name: Strict SSH access 15 | lineinfile: > 16 | dest=/etc/ssh/sshd_config 17 | state=present 18 | regexp='^#?{{ item.key }}' 19 | line='{{ item.key }} {{ item.value }}' 20 | validate='/usr/sbin/sshd -t -f %s' 21 | with_items: 22 | - { key: 'PermitRootLogin', value: 'no'} 23 | - { key: 'PasswordAuthentication', value: 'no'} 24 | - { key: 'AllowGroups', value: "sshusers"} 25 | - { key: 'MaxAuthTries', value: "5"} 26 | - { key: 'LoginGraceTime', value: "60"} 27 | - { key: 'MaxSessions', value: "5"} 28 | - { key: 'MaxStartups', value: "10:30:60"} 29 | notify: restart sshd 30 | 31 | - name: Delete server ssh keys 32 | shell: rm -rf /etc/ssh/ssh_host_* 33 | 34 | - name: Re-generate server ssh keys 35 | command: dpkg-reconfigure openssh-server 36 | notify: restart sshd 37 | 38 | - name: Configure firewall for SSH 39 | ufw: rule=allow name=OpenSSH 40 | 41 | - name: Enable firewall 42 | ufw: state=enabled 43 | 44 | -------------------------------------------------------------------------------- /tasks/software.yml: -------------------------------------------------------------------------------- 1 | - name: Install updates 2 | apt: upgrade=dist update_cache=yes 3 | 4 | - name: Set system wide mail settings 5 | debconf: > 6 | name=postfix 7 | question="{{ item.q }}" 8 | value="{{ item.v }}" 9 | vtype="{{ item.t }}" 10 | with_items: 11 | - { q: 'postfix/main_mailer_type', t: 'select', v: 'Internet Site' } 12 | - { q: 'postfix/mailname', t: 'string', v: 'localhost' } 13 | - { q: 'postfix/destinations', t: 'string', v: 'localhost.localdomain,localhost,{{ do_hostname }}' } 14 | 15 | - name: Install essintial packages 16 | apt: pkg="{{ item }}" state=latest 17 | with_items: 18 | - ntp 19 | - tmux 20 | - fail2ban 21 | - postfix 22 | 23 | - name: Configure postfix 24 | lineinfile: > 25 | dest=/etc/postfix/main.cf 26 | state=present 27 | backup=yes 28 | regexp='^{{ item.key }}\s+=\s+.*$' 29 | line='{{ item.key }} = {{ item.value }}' 30 | with_items: 31 | - { key: 'inet_interfaces', value: 'loopback-only' } 32 | notify: restart postfix 33 | 34 | - name: Update mail aliases database 35 | lineinfile: > 36 | dest=/etc/aliases 37 | state=present 38 | backup=yes 39 | regexp='^{{ item.key }}:\s+.*$' 40 | line='{{ item.key }}{{':'}} {{ item.value }}' 41 | with_items: 42 | - { key: 'root', value: '{{ do_admin }}' } 43 | - { key: '{{ do_admin }}', value: '{{ do_email }}' } 44 | notify: 45 | - update aliases 46 | 47 | - name: Ensure services is running and enabled 48 | service: name="{{ item }}" state=running enabled=yes 49 | with_items: 50 | - ntp 51 | - fail2ban 52 | - postfix 53 | -------------------------------------------------------------------------------- /tasks/users.yml: -------------------------------------------------------------------------------- 1 | - name: Create user groups 2 | group: name="{{ item }}" state=present 3 | with_items: 4 | - "{{ sudo_group }}" 5 | - "{{ ssh_group }}" 6 | 7 | - name: Change ROOT password 8 | user: > 9 | name=root 10 | groups="{{ ssh_group }}" 11 | password="{{ item }}" 12 | with_password: 13 | "../credentials/{{ do_hostname }}/root encrypt=sha512_crypt" 14 | 15 | - name: Create admin user 16 | user: > 17 | name="{{ do_admin }}" 18 | comment="Administrator account" 19 | state=present 20 | groups="{{ sudo_group }},{{ ssh_group }}" 21 | password="{{ item }}" 22 | append=yes 23 | with_password: 24 | "../credentials/{{ do_hostname }}/{{ do_admin }} encrypt=sha512_crypt" 25 | 26 | - name: Deploy ssh public key 27 | authorized_key: user="{{ do_admin }}" key="{{ my_pub_key }}" 28 | -------------------------------------------------------------------------------- /vars.yml: -------------------------------------------------------------------------------- 1 | do_hostname: domain.tld 2 | do_admin: admin1 3 | do_email: drop@domain.tld 4 | my_pub_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" 5 | 6 | # digital ocean credentials 7 | api_key: 8 | client_id: 9 | 10 | ssh_group: sshusers 11 | sudo_group: sudousers 12 | --------------------------------------------------------------------------------