2019-11-03

Automating Azure Deployments with Ansible on OpenBSD: Part 3

Way back when, you'd have to keep your password somewhere in plain text because you needed it in an important script you needed to run. Maybe you chmoded and chowned it so that people couldn't get to it, but at the end of the day it always just worked without you having to think about it; so much so that you forgot it was there and then you accidentally checked that script into source control and uploaded it to the world and you didn't realize it until, like, at best, an hour later. There's a better way.

In general when I'm uploading VM images to Azure, I've already configured a remote management user. OpenBSD makes this trivial by allowing you to add a user during autoinstall(8):

Setup a user = remoteadmin
Full name for user = remoteadmin
Password for user = *************
Public ssh key for user remoteadmin = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILool4pwv0BHb25YZjzq36Wl4GgqRvdFfKuDHvcSm89I

My Ansible host has this SSH key, so it can manage any VM that is created from this image. Remember to create /etc/doas.conf if you need to elevate your permissions!

Azure VM deployments require a username and (usually) a password or else the deployment won't pass the validation step. (Microsoft really doesn't want you proverbially locking your keys in the car with your baby inside it.) I don't want to define another superuser for my VMs, but since I'm forced to do so, I am going to make it something no one is ever going to be able to use, not even me.

To accomplish this, I end up needing to randomly generate a username and a password — credentials I'm never going to use — just to make Azure happy. I discovered a neat way to make this happen:

bash-5.0$ cat credentials-new-fromshell.yml
---
- hosts: localhost
  gather_facts: no

  tasks:
  - name: generate username
    run_once: yes
    local_action:
      shell: head -10 /dev/urandom | strings | tr -cd '[:lower:]' | tr -d 'aeiou' | fold -w12 | head -1
    register: username

  - name: generate password
    run_once: yes
    local_action:
      shell: head -30 /dev/urandom | strings | tr -cd '[:punct:]''[:alnum:]' | tr -d "\\\//\#\'\"\`\$\[\]\{\}" | fold -w72 | head -1
    register: password

  - name: print username
    debug:
      msg: "{{username.stdout}}"

  - name: print password
    debug:
      msg: "{{password.stdout}}"

# END

The run_once part is also critical: if you deploy multiple VMs, you want to make sure they all get the same username and password values you generated.

This works, but it feels sloppy. You'd think that a machine management platform would have a builtin solution to the problem of generating passwords. Fortunately, Ansible can handle this with the password plugin, but it isn't nearly as well known as it should be:

bash-5.0$ cat credentials-new-frombuiltin.yml
---
- hosts: localhost
  gather_facts: no

  tasks:
  - name: generate credential
    run_once: yes
    set_fact:
      username: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters')|lower }}"
      password: "{{ lookup('password', '/dev/null length=64 chars=ascii_letters,digits,punctuation') }}"

  - name: print credential
    debug:
      msg: "username: {{username}} password: {{password}}"

# END

This is how I recommend you generate your random usernames and passwords. So to put this into an example:

bash-5.0$ cat vm-randomusername-new.yml
---
- hosts: localhost
  gather_facts: no
  vars:
    service_prefix: my-service
    rgroup_name: "{{service_prefix}}-rgroup"
    location: westus2
    vm_size: Standard_A1

  tasks:
  - name: generate credential
    run_once: yes
    set_fact:
      username: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters')|lower }}"
      password: "{{ lookup('password', '/dev/null length=64 chars=ascii_letters,digits,punctuation') }}"

    # (vnet, network interface resources go here)

    - name: create vm
      azure_rm_virtualmachine:
        name: "{{service_prefix}}-ubuntu1"
        resource_group: "{{rgroup_name}}"
        location: "{{location}}"
        vm_size: "{{vm_size}}"
        admin_username: "{{username}}"
        admin_password: "{{password}}"
        os_type: Linux
        image:
          publisher: Canonical
          offer: UbuntuServer
          sku: 18.04-LTS
          version: latest
        os_disk_name: "{{service_prefix}}-osdisk"
        managed_disk_type: Standard_LRS
        network_interface_names: "{{service_prefix}}-nic"
        started: no
        state: present

  - name: print credential
    debug:
      msg: "username: {{username}} password: {{password}}"

# END

This is the last problem I needed to solve in order to completely automate my OpenBSD VM deployment pipeline. I can provision a VM with a PowerShell script and an autoinstall(8) config file. I can set it up with an Ansible playbook and a bevy of assigned roles. I can upload the .VHD file with a wrapper script around blobxfer and now, finally, I can create an Azure image and deploy VMs with an Ansible playbook using Azure management modules.

Life is good.

No comments: