I bought OVH VPS at half price during Black Friday.

For the past few years, I ssh into the server and used the command to pull Docker images and config files from a git repo. This year, I tested out Ansible to automate setting up the VPS. The learning curve wasn’t too bad but I did run into a few problems here and there.

New User

This role starts firewall with ssh port, creates a new user for ssh and remove root ssh login.

- name: install unattended upgrade
  apt:
    name: "{{ item }}"
  loop:
    - unattended-upgrades
    - fail2ban
 
- name: unattended upgrade config
  template:
    src: 10periodic.j2
    dest: /etc/apt/apt.conf.d/10periodic
    owner: root
    group: root
    mode: 0644
 
- name: start ufw with port 22
  ufw:
    state: enabled
    rule: allow
    port: 22
 
- name: Add a new user
  user:
    name: "{{ user_name }}"
    groups: sudo
    append: yes
    shell: /bin/bash
    password: "{{ user_password }}"
 
- name: Add ssh key
  authorized_key:
    user: "{{ user_name }}"
    key: "{{ key_file }}"
 
- name: Setup ssh config
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
  loop:
    - regexp: '^PasswordAuthentication'
      line: 'PasswordAuthentication no'
    - regexp: '^PermitRootLogin'
      line: 'PermitRootLogin no'
  notify:
    - restart ssh

Secure VPS

This makes the server more secure and email me daily report. It’s pretty crazy how many ssh attempts my new VPS gets everyday.

- name: install postfix, fail2ban and logwatch
  apt:
    name: "{{ item }}"
    update_cache: yes
  loop:
    - unattended-upgrades
    - postfix
    - logwatch
    - fail2ban
 
- name: unattended upgrade config
  template:
    src: 10periodic.j2
    dest: /etc/apt/apt.conf.d/10periodic
    owner: root
    group: root
    mode: 0644
 
- name: Create fail2ban config
  template:
    src: jail.local.j2
    dest: /etc/fail2ban/jail.local
    owner: root
    group: root
    mode: 0644
  notify:
    - restart fail2ban
 
- name: Create postfix conf
  template:
    src: main.cf.j2
    dest: "{{ postfix_conf_dir }}/main.cf"
    owner: root
    group: root
    mode: 0644
 
- name: Create sasl password file
  template:
    src: mailgun.j2
    dest: "{{ postfix_conf_dir }}/mailgun"
    owner: root
    group: root
    mode: 0600
 
- name: Update postfix table
  command: postmap "{{ postfix_conf_dir }}/mailgun"
  notify:
    - restart postfix
 
- name: Setup logwatch cron
  lineinfile:
    path: /etc/cron.daily/00logwatch
    regexp: "^/usr/sbin/logwatch"
    line: "/usr/sbin/logwatch --mailto {{ my_email }} --detail high"

Docker

I use gitlab to host some docker images. I utilise private repo to build docker images with sensitive data. I’m not sure it’s a good idea to keep username, password and api key inside images. Would it be better to expose those via env variables?

- name: Install packages
  apt:
    name: "{{ item }}"
    update_cache: yes
  loop:
    - apt-transport-https
    - ca-certificates
    - python3-pip
 
- name: Add Docker's GPG key
  apt_key:
    url: https://download.docker.com/linux/ubuntu/gpg
    id: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88
 
- name: Add Docker repo
  apt_repository:
    repo: "{{ docker_apt_repo }}"
 
- name: Install docker
  apt:
    name: docker-ce
 
- name: Install docker compose
  get_url:
    url: "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-Linux-x86_64"
    dest: /usr/local/bin/docker-compose
    mode: 0755
 
- name: Install docker and docker-compose pip
  pip:
    name: "{{ item }}"
  loop:
    - docker
    - docker-compose
 
- name: Add docker group
  group:
    name: docker
 
- name: Add user to docker group
  user:
    name: "{{ user_name }}"
    groups: docker
    append: yes
  notify:
    - reboot
 
- name: Log into gitlab docker repo
  docker_login:
    registry: registry.gitlab.com
    username: "{{ gitlab_username }}"
    password: "{{ gitlab_password }}"
    config_path: "/home/{{ user_name }}/.docker/config.json"
 
- name: Copy docker-compose.yml to remote
  template:
    src: docker-compose.yml.j2
    dest: "/home/{{ user_name }}/docker-compose.yml"
    owner: "{{ user_name }}"
    group: "{{ user_name }}"
 
- name: Start docker compose
  become: false
  docker_service:
    project_src: "/home/{{ user_name }}"

VPN

- name: Add wireguard ppa
  apt_repository:
    repo: "ppa:wireguard/wireguard"

- name: Install wireguard
  apt:
    name: wireguard

- name: Allow forwarding
  lineinfile:
    path: /etc/sysctl.conf
    regexp: "{{ item.regexp }}"
    line: "{{item.line }}"
  loop:
    - regexp: '^#net.ipv4.ip_forward'
      line: 'net.ipv4.ip_forward=1'
    - regexp: '^#net.ipv6.conf.all.forwarding'
      line: 'net.ipv6.conf.all.forwarding=1'
  notify:
    - restart sysctl

- name: Open ufw port
  ufw:
    state: enabled
    rule: allow
    port: "{{ wg_port }}"

- name: Create wg config
  template:
    src: wg.conf.j2
    dest: "{{ wg_config_dir }}/wg.conf"
    owner: root
    group: root
    mode: 0600

- name: Start wg
  service:
    name: wg-quick@wg
    state: started
    enabled: yes

- name: Allow ovh ipv6
  template:
    src: 50-cloud-init.yaml.j2
    dest: "{{ netplan_config_dir }}/50-cloud-init.yaml"
    owner: root
    group: root
    mode: 0644
  when: (ansible_distribution == "Ubuntu" and ansible_distribution_major_version == "18")
  notify:
    - restart netplan