Create non-root user and disable root SSH in Ansible
I'm trying to write an Ansible playbook to bootstrap my servers. By default on Linode I can only login as root with a password, so my playbook logs in as root, creates a non-root user with an SSH key, and disables root and password SSH.
This is a problem because I now can't run that playbook again since root login is disabled! I would like the playbook to be idempotent and not have to add and remove hosts after bootstrapping them.
Solution 1:
I like to do it this way:
- hosts: all
remote_user: root
gather_facts: no
tasks:
- name: Check ansible user
command: ssh -q -o BatchMode=yes -o ConnectTimeout=3 ansible@{{ inventory_hostname }} "echo OK"
delegate_to: 127.0.0.1
changed_when: false
failed_when: false
register: check_ansible_user
- block:
- name: Create Ansible user
user:
name: ansible
comment: "Ansible user"
password: $6$u3GdHI6FzXL01U9q$LENkJYHcA/NbnXAoJ1jzj.n3a7X6W35rj2TU1kSx4cDtgOEV9S6UboZ4BQ414UDjVvpaQhTt8sXVtkPvOuNt.0
shell: /bin/bash
- name: Add authorized key
authorized_key:
user: ansible
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
exclusive: yes
- name: Allow sudo for ansible
copy:
content: ansible ALL=(ALL) ALL
dest: /etc/sudoers.d/ansible
mode: 0600
when: check_ansible_user | failed
I try to connect to the remote host with my ansible user. If this is impossible (on the first run), I connect as root and create the ansible user along with its authorized_keys
file and sudo
rights.
On subsequent runs, connecting as ansible user works, so the block of tasks can be skipped.
Once the remote host is bootstrapped, I can go on with the ansible user and become
:
- hosts: all
remote_user: ansible
become: yes
roles:
- ...
Solution 2:
I would do the following:
- create a role (something like 'base') where you (amongst other things), create a suitable user (and sudo rules) for ansible to use
- create or adapt your role for SSH, to manage
sshd_config
(I would tend to recommend you manage the entire file, using atemplate
, but that is up to you), and disable root logins - make your SSH role depend on the base role, e.g. using meta.
For the first role (the base one), I tend to use something like:
name: base | local ansible user | create user
user:
name: "{{ local_ansible_user }}"
group: "{{ local_ansible_group }}"
home: "/home/{{ local_ansible_user }}"
state: present
generate_ssh_key: "{{ local_ansible_generate_key }}"
ssh_key_bits: 4096
ssh_key_type: rsa
tags:
- ansible
- local_user
- name: base | local ansible user | provision authorised keys
authorized_key:
user: "{{ local_ansible_user }}"
state: present
key: "{{ item }}"
with_items: "{{ local_ansible_authorised_keys }}"
tags:
- ansible
- authorised_keys
For the SSH config, I would use:
- name: openssh | server | create configuration
template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: "0640"
validate: "/usr/sbin/sshd -tf %s"
notify:
- openssh | server | restart
tags:
- ssh
- openssh
Ansible's role dependencies are documented here.
You could also just use ordering within your playbook to do this.
I have some ansible stuff on github (from which the above is taken), if you want to see it in context
Solution 3:
If you create your servers on Linode with the linode module you could register the return value
of the linode
task and include the the bootstrap tasks with an condition checking the outout of the linode task. That should be idempotent. Try something like this:
- linode:
api_key: 'longStringFromLinodeApi'
name: linode-test1
plan: 1
datacenter: 2
distribution: 99
password: 'superSecureRootPassword'
private_ip: yes
ssh_pub_key: 'ssh-rsa qwerty'
swap: 768
wait: yes
wait_timeout: 600
state: present
register: linode_node
- include: bootstrap.yml
when: linode_node.changed
bootstrap.yml
would than contain all the tasks necessary to disable ssh root login and so on.