├── .gitignore ├── README.md ├── ansible.cfg ├── inventory.sample ├── main.yml ├── main_security.yml ├── main_setup.yml └── roles ├── security └── tasks │ └── main.yml └── setup ├── tasks ├── configure_machine.yml ├── create_aliases.yml ├── create_users.yml ├── install_essentials.yml ├── main.yml └── update_machine.yml └── templates ├── bash_aliases.j2 └── cpufrequtils.j2 /.gitignore: -------------------------------------------------------------------------------- 1 | inventory 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secure Server Setup 2 | 3 | Typically, a cloud server provides a machine with root access and insecure setup. This ansible playbook is designed to fix that. It is based on Ubuntu 20.04 (LTS) or 21.04 image, but it should be applicable to other Ubuntu images. 4 | 5 | ## Set Up Server 6 | 7 | Copy inventory file 8 | 9 | ```bash 10 | cp inventory.sample inventory 11 | ``` 12 | 13 | Update information in the inventory file. Mostly like you will need to update the server IP and hostname fields. Then run main ansible playbook. 14 | 15 | ```bash 16 | ansible-playbook main.yml 17 | ``` 18 | 19 | The main ansible playbook will both set up the machine and also secure the machine. 20 | 21 | ### Set up machine: 22 | 23 | 1. Create Users: Create "ansible" and "ubuntu" users and allow them sudo access. The idea is to have "ansible" to run ansible playbooks automatically and "ubuntu" for ad hoc manual server management. ("ubuntu" is my chosen user. You can configure it in inventory file) 24 | 2. Configure Machine: Set the hostname (based on inventory file) and timezone (Los Angeles Time) 25 | 3. Create aliases for easy server management 26 | 4. Update machine: Simply update and upgrade all applications shipped with the OS. 27 | 5. Install some essential software 28 | 6. Optionally install node exporter (configurable in inventory) 29 | 7. Optionally install promtail (configurable in inventory) 30 | 31 | ### Secure machine: 32 | 33 | 1. Install firewall 34 | 2. Install fail2ban 35 | 3. Disable the default ssh port of 22, and set up the alternative port. 36 | 4. Enable firewall to allow the alternative port and deny 22. 37 | 5. Disable root account access 38 | 6. Disable password authentication. 39 | 40 | After running the main playbook, you can no longer re-run these two playbooks because you no longer have the root account access. Instead, you need to use "ubuntu" or "ansible" users to access server using ssh key through the alternative port. 41 | 42 | ## Other considerations 43 | 44 | You may want to experiment the machine setup without the security lock-down, or vice versa. The repo provides separate playbooks for setup and security 45 | 46 | Setup: 47 | 48 | ```bash 49 | ansible-playbook main_setup.yml 50 | ``` 51 | 52 | Security: 53 | 54 | ```bash 55 | ansible-playbook main_security.yml 56 | ``` 57 | 58 | That's it! 59 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | INVENTORY = inventory 3 | -------------------------------------------------------------------------------- /inventory.sample: -------------------------------------------------------------------------------- 1 | [server] 2 | 192.168.0.20 3 | 4 | [all:vars] 5 | ansible_ssh_user=root 6 | ansible_ssh_private_key_file="~/.ssh/id_rsa" 7 | 8 | hostname="SERVER_NAME" 9 | deployment_user="USER_NAME" 10 | ssh_port=22222 11 | disable_root=true 12 | 13 | # Node Exporter 14 | node_exporter=false 15 | node_exporter_version='1.2.2' 16 | 17 | # Promtail 18 | promtail=false 19 | promtail_version='2.4.1' 20 | promtail_monitor='http://192.168.0.0:3100' 21 | promtail_name=something 22 | -------------------------------------------------------------------------------- /main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | gather_facts: no 4 | become: true 5 | roles: 6 | - setup 7 | - security 8 | -------------------------------------------------------------------------------- /main_security.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | gather_facts: no 4 | become: true 5 | roles: 6 | - security 7 | -------------------------------------------------------------------------------- /main_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | gather_facts: no 4 | become: true 5 | roles: 6 | - setup 7 | -------------------------------------------------------------------------------- /roles/security/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install ufw 3 | apt: 4 | name: ufw 5 | state: present 6 | update_cache: yes 7 | 8 | - name: Install fail2ban 9 | apt: 10 | name: fail2ban 11 | state: present 12 | update_cache: yes 13 | 14 | - name: Restart fail2ban 15 | service: 16 | name: fail2ban 17 | state: restarted 18 | enabled: true 19 | changed_when: false 20 | 21 | - name: Open ssh port 22 | ufw: 23 | rule: allow 24 | port: '{{ ssh_port }}' 25 | proto: tcp 26 | 27 | - name: Deny private ip probing 28 | ufw: 29 | rule: deny 30 | proto: any 31 | direction: out 32 | src: '{{ item }}' 33 | loop: 34 | - 10.0.0.0/8 35 | - 172.16.0.0/12 36 | - 192.168.0.0/16 37 | - 100.64.0.0/10 38 | - 198.18.0.0/15 39 | - 169.254.0.0/16 40 | 41 | - name: Deny port 22 42 | ufw: 43 | rule: deny 44 | port: 22 45 | proto: tcp 46 | 47 | - name: Disable port 22 on ssh config 48 | lineinfile: 49 | dest: '/etc/ssh/sshd_config' 50 | regex: '^Port 22' 51 | line: 'Port {{ ssh_port }}' 52 | state: present 53 | create: true 54 | 55 | - name: Disable root login over SSH 56 | lineinfile: 57 | dest: '/etc/ssh/sshd_config' 58 | regexp: '^PermitRootLogin' 59 | line: 'PermitRootLogin no' 60 | state: present 61 | when: disable_root|default(true)|bool == true 62 | 63 | - name: Disable password login 64 | lineinfile: 65 | dest: '/etc/ssh/sshd_config' 66 | regexp: '^PasswordAuthentication' 67 | line: 'PasswordAuthentication no' 68 | state: present 69 | 70 | - name: Restart SSH 71 | service: 72 | name: ssh 73 | state: restarted 74 | changed_when: false 75 | 76 | - name: Enable firewall 77 | ufw: 78 | state: enabled 79 | -------------------------------------------------------------------------------- /roles/setup/tasks/configure_machine.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: change hostname 3 | hostname: 4 | name: '{{ hostname }}' 5 | 6 | - name: Update Timezone to my time 7 | shell: 'timedatectl set-timezone America/Los_Angeles' 8 | changed_when: false 9 | 10 | - name: Update file limits for user 11 | blockinfile: 12 | path: /etc/security/limits.conf 13 | insertbefore: '# End of file' 14 | marker: '# {mark} ANSIBLE MANAGED BLOCK' 15 | block: | 16 | {{ deployment_user }} soft nofile 1048576 17 | {{ deployment_user }} hard nofile 1048576 18 | {{ deployment_user }} soft nproc 1048576 19 | {{ deployment_user }} hard nproc 1048576 20 | 21 | - name: Set governor as performance 22 | template: 23 | src: cpufrequtils.j2 24 | dest: '/etc/default/cpufrequtils' 25 | owner: 'root' 26 | group: 'root' 27 | mode: '0644' 28 | 29 | - name: start cpufrequtils service 30 | systemd: 31 | name: cpufrequtils 32 | state: restarted 33 | daemon_reload: yes 34 | enabled: yes 35 | changed_when: false 36 | -------------------------------------------------------------------------------- /roles/setup/tasks/create_aliases.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create alias file 3 | template: 4 | src: bash_aliases.j2 5 | dest: '/home/{{ deployment_user }}/.bash_aliases' 6 | owner: '{{deployment_user}}' 7 | group: '{{deployment_user}}' 8 | mode: '0644' 9 | -------------------------------------------------------------------------------- /roles/setup/tasks/create_users.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create Ansible user 3 | user: 4 | name: ansible 5 | comment: 'Ansible user' 6 | shell: /bin/bash 7 | 8 | - name: Add ansible authorized key 9 | authorized_key: 10 | user: ansible 11 | key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" 12 | exclusive: yes 13 | 14 | - name: Allow sudo for ansible 15 | copy: 16 | content: 'ansible ALL=(ALL) NOPASSWD: ALL' 17 | dest: /etc/sudoers.d/ansible 18 | mode: '0600' 19 | 20 | - name: Create administrator user 21 | user: 22 | name: '{{ deployment_user }}' 23 | comment: 'admin user' 24 | shell: /bin/bash 25 | 26 | - name: Add administrator authorized key 27 | authorized_key: 28 | user: '{{ deployment_user }}' 29 | key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" 30 | exclusive: yes 31 | 32 | - name: Allow sudo for administrator 33 | copy: 34 | content: '{{ deployment_user }} ALL=(ALL) NOPASSWD: ALL' 35 | dest: '/etc/sudoers.d/{{ deployment_user }}' 36 | mode: '0600' 37 | -------------------------------------------------------------------------------- /roles/setup/tasks/install_essentials.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install essentials 3 | apt: 4 | name: 5 | - net-tools 6 | - ncdu 7 | - vnstat 8 | - tree 9 | - htop 10 | - zip 11 | - git 12 | - ufw 13 | - jq 14 | - bash-completion 15 | - cpufrequtils 16 | - ccze 17 | - nginx 18 | state: latest 19 | update_cache: true 20 | -------------------------------------------------------------------------------- /roles/setup/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create users 3 | import_tasks: create_users.yml 4 | 5 | - name: Install essentials 6 | import_tasks: install_essentials.yml 7 | 8 | - name: Configure Machine 9 | import_tasks: configure_machine.yml 10 | 11 | - name: Create Aliases 12 | import_tasks: create_aliases.yml 13 | 14 | - name: Update machine 15 | import_tasks: update_machine.yml 16 | -------------------------------------------------------------------------------- /roles/setup/tasks/update_machine.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update and upgrade the machine 3 | apt: 4 | upgrade: dist 5 | update_cache: true 6 | -------------------------------------------------------------------------------- /roles/setup/templates/bash_aliases.j2: -------------------------------------------------------------------------------- 1 | alias ~="cd ~" 2 | alias ..="cd .." 3 | alias ll="ls -al" 4 | alias h="history" 5 | alias upgrade="sudo apt update && sudo apt upgrade -y" 6 | alias system="cd /etc/systemd/system" 7 | alias home="cd ~" 8 | alias dua="du -h --max-depth=1 | sort -rh" 9 | alias hgrep="history | grep " 10 | alias reload="sudo systemctl daemon-reload" 11 | alias e="exit" 12 | alias log="sudo journalctl -fu" 13 | alias gc="git checkout" 14 | alias gt="git tag" 15 | alias gp="git pull" 16 | start() { sudo service "$@" start; } 17 | restart() { sudo service "$@" restart; } 18 | stop() { sudo service "$@" stop; } 19 | see() { log "$@" | grep committed; } 20 | cheat() { 21 | curl -m 10 "http://cheat.sh/${1}" 2>/dev/null || printf '%s\n' "[ERROR] Something broke" 22 | } 23 | loc() { sudo journalctl -fu "$@" | ccze; } 24 | alias mi="make install" 25 | dlog() { docker logs "$@" -f --since 2m; } -------------------------------------------------------------------------------- /roles/setup/templates/cpufrequtils.j2: -------------------------------------------------------------------------------- 1 | ENABLE="true" 2 | GOVERNOR="performance" --------------------------------------------------------------------------------