Episodic Genius

occurring occasionally and at irregular intervals

DO Droplets and Root

I’ve used DigitalOcean (DO) before. I think I spun up my first tiny droplet a few years ago and I ran this blog on one for quite a while. However, it had been a while and I never got into it enough to have needed much automation. Now, I’m a DO employee and I will be using it frequently.

The problem is that DO droplets come up with access only to the root user. I don’t think it is right. For one thing, the root user is the most common user out there. Every UNIX system has one so it is a target for attackers. Maybe it isn’t that much worse than the other common default – that is to begin with another relatively common user like ubuntu and give that user passwordless sudo access – but I believe it is a better starting point and can easily be secured further. DO has so many other great things going for it, I’m willing to let this one slide … for now.

Ignoring security concerns, the immediate problem for me was that all the ansible playbooks I’ve accumulated begin with the assumption that it will login as an unprivileged user first and then use privilege escalation when necessary. My normal flows were broken.

Enter my new do-bootstrap.yaml playbook:

# Create a non-privileged user with sudo access and disable root login
- hosts: all
    username: carl
    ansible_user: root
  - name: Add group
      name: "{{ username }}"
      state: present
  # New user's password is locked (i.e. no password access allowed)
  - name: Add user
      name: "{{ username }}"
      group: "{{ username }}"
      groups: sudo
      shell: /bin/bash
      append: yes
  # I assume I always want to use the first key in local authorized_keys
  - name: get key from file
      module: shell
      _raw_params: head -n 1 ~/.ssh/authorized_keys
    register: ssh_key_action
  - name: install ssh key
      user: "{{ username }}"
      key: "{{ ssh_key_action.stdout }}"
  - name: enable passwordless sudo
    # This might be more elegant if I create a file in /etc/sudoers.d/
      path: /etc/sudoers
      state: present
      regexp: '^%sudo'
      line: '%sudo ALL=(ALL) NOPASSWD: ALL'
      validate: 'visudo -cf %s'
  - name: Disable root login
      dest: /etc/ssh/sshd_config
      regexp: "^PermitRootLogin "
      line: "PermitRootLogin no"
      - restart sshd
  - name: Disable password login
    # I think this is already set this way, but just in case
      dest: /etc/ssh/sshd_config
      regexp: "^PasswordAuthentication "
      line: "PasswordAuthentication no"
      - restart sshd

    - name: restart sshd
        name: sshd
        state: restarted
        daemon_reload: yes

It is now the first thing that I run after booting a new droplet on DO. It gets me to the point where many other cloud images start and fits well into my automation. You might notice that this playbook is not idempotent. Once I run it, it cannot be run again because it assumes that it is running under a privileged user and does not bother to escalate. This is okay given where I call it in my VM creation process. I have my own tool that will boot the VM using the API and then immediate run a couple of one-off playbooks like this. Then, I use a second set of idempotent playbooks which I run periodically.

One nice advantage to this flow is that I get to pick my own username. I could always do that with any other image, but I never bothered. This forced me into it and I am choosing to count that as a positive outcome.