Linux:Ansible-Ivo

From Cheatsheet
Jump to navigationJump to search


This page was written and contributed by Ivo Palli.

TODO: v vaults v use external variable files

  • actions on regex (kan met lineinfile)

v create crontab v create repo v create, download and use roles (!)

  • configure selinux
  • jinja2 (.j2) templates (gewoon een text file waar je variables Template:Xx in kan zetten, en dan kopieert met module template
  • check info over installeren van python en instellen

v ansible-galaxy

  • tags

Ansible

  • On the control node, set up a separate account 'ansible' with SSH keys to the three client nodes
  • On ALL nodes. We do this first so we can ssh-copy-id over the credentials later:
useradd ansible                                                       # Can be any username
echo password | passwd --stdin ansible
echo "ansible ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/ansible       # Make sure this user can do root commands
  • On the CONTROL node:
cat << 'EOF' >> /etc/hosts                                           # We're not setting up DNS
192.168.1.1    control
192.168.1.2    ansible1
192.168.1.3    ansible2
192.168.1.4    ansible3
EOF

echo 'control.example.com' > /etc/hostname

# Ubuntu:
apt-add-repository --yes --update ppa:ansible/ansible
apt install ansible

# RHEL:
yum -y install python3 python3-pip
pip3 install --upgrade pip
alternatives --set python /usr/bin/python3

su - ansible
pip3 install ansible --user                                          # We're installing this to the user account only
# If you get after "connection broken by NewConnectionError", check your DNS
ansible --version

ssh-keygen                                                           # Default settings for everything. Just hit Enter
ssh-copy-id ansible1
# Do so for all nodes

ssh ansible1 sudo yum -y install python3 python3-pip                 # Install and set prerequisites on all clients
ssh ansible1 sudo alternatives --set python /usr/bin/python3
# Do so for all nodes

ansible.cfg

[defaults]
inventory=inventory
remote_user=ansible
host_key_checking=false
deprecation_warnings=false
forks=10
roles_path = roles:/usr/share/ansible/roles

[privilege_escalation]
become=false
become_method=sudo
become_user=root
become_ask_pass=false
  • You can view all settings you can set with: ansible-config list
  • You can check for spelling errors in the config with ansible-config dump --only-changed
  • If the exam says 'Privilege escalation is disabled by default.' then set 'become=false' and run playbooks with the 'ansible-playbook -b' switch (on set 'become: true' in the playbook itself) .
  • Instead of 'fork', you can also batch servers in groups with 'serial'. https://docs.ansible.com/ansible/latest/user_guide/playbooks_strategies.html
  • Check /etc/ansible/ansible.cfg if you're not sure about options

inventory

[an1]
ansible1

# serv1 is an alias
[an2]
ansible2
serv1 ansible_hostname=serv1.longdomain.nl

[hosts_with_ranges]
server[1:3].example.com
192.168.1.[1:5]

[variable_single_host_example]
ansible1 my_var=5 my_other_var=6

# Define vars for every host in a group, in this case group an2
[an2:vars]
my_var=7

# Create group an with subgroups an1 and an2
[an:children]
an1
an2

You can also add variables per host in a 'host_vars' directory with files names as the host. Or in a 'group_vars' directory, named as the defined groups.

Adhoc examples

ansible all -m user -a "name=lisa"                                          # Adds a user lisa to all specified hosts
ansible all -m command -a "id lisa"                                         # Shows the attributes of user lisa
ansible all -m user -a "name=lisa state=absent remove=yes"                  # Removes a user (if present) and removes the home directory

ansible all -m ping                                                         # See if the host is responsive

ansible ansible1 -m copy -a 'content="Hello world" dest=/tmp/hello'         # Put something in a file

ansible ansible1 -m raw -a "yum -y install python3"                         # Since 'raw' doesn't use python, we can use it to set up python

ansible localhost -m debug -a "msg='{{lookup('file', '/etc/motd')}}'"       # Use lookup to insert the content of a file

ansible-doc

ansible-doc --list                                       # Show all modules
ansible-doc user                                         # Show the documentation for modules 'user'
ll /usr/lib/python3/dist-packages/ansible/modules/       # See all the modules

Creating a batch file to set up accounts for ansible

Ok, so a host NEEDS python installed to run ansible commands. If no python is present, you can still use the 'raw' module in ansible since this has no dependencies. You can use the ping module to see if ansible would work (ping in this context is not the network ping).

#!/bin/bash

# Execute this as root. (Assuming root has passwordless access to all servers)
# (If not, I suggest ssh-copy-id)

export ANSIBLE_CONFIG=/home/automation/plays
export ANSIBLE_REMOTE_USER=root

ansible all -m ping
ansible all -m raw -a "adduser automation"
ansible all -m raw -a "mkdir /home/automation/.ssh"
# Note that you can read in id_rsa.pub in nano with CTRL-R
ansible all -m raw -a "echo 'ssh-rsa AAAxxxxxx' > /home/automation/.ssh/authorized_keys"
# TAKE NOTE OF THE FORMAT! Check the existing %wheel in /etc/sudoers when in doubt!
ansible all -m raw -a "echo -e 'automation\tALL=(ALL)\tNOPASSWD: ALL'" > /etc/sudoers.d/automation"

but if ping fails, also do:

ssh ansible1 sudo yum -y install python3 python3-pip                 # Install and set prerequisites on all clients
ssh ansible1 sudo alternatives --set python /usr/bin/python3

If ping works (i.e. ansible works 100% on the target server) then you can use ansible native commands:

#!/bin/bash

export ANSIBLE_CONFIG=/home/automation/plays
export ANSIBLE_REMOTE_USER=root

ansible all -m user -a "name=automation state=present"
ansible all -m file -a "path=/home/automation/.ssh state=directory"
ansible all -m copy -a "src=/home/automation/.ssh/id_rsa.pub dest=/home/automation/.ssh/authorized_keys"
ansible all -m copy -a "dest=/etc/sudoers.d/automation content='automation\tALL=(ALL)\tNOPASSWD: ALL\n'"

Important modules

  • ping

You can use this to test is a host is reachable and able to run Ansible playbooks

  • user

Handles user creation, modification, creating SSH keys, etc. Generate SHA512 passwords with user_password=$(openssl passwd -6 my_password)

  • dnf, yum, apt, package

Package management, although 'package' tries to be platform agnostic

  • service

start, stop, restart, enable services

  • copy

speaks for itself

  • file

attributes, symlinks and directories. You can copy files around in the remote filesystem.

  • lineinfile

basically "sed -i" with regex support, or adding lines if they don't exist

  • archive

create or extract archives

  • shell / command

Execute CLI commands of which the output you can store in a variable with 'register: xxx'. Use 'xxx.stdout' in your script

  • setup

Gives you a dump of all the 'facts' of the target server. Use the 'filter' option to reduce the output.

  • include

Lets you insert other playbooks

  • mysql_user

Do MySQL user related things

  • debug

Help you debug playbooks but can also be used to simple output a message to the user with 'msg:'

https://opensource.com/article/19/9/must-know-ansible-modules

Playbooks / YAML

  • NEVER USER TABS!
  • Most commonly used indentation is 2 spaces
  • Elements on the same level are indented the same
  • List items start with a dash (-)
  • Child elements are indented further
  • A playbook starts with '---' and ends with '...'
  • A playbook has at least 1 play (i.e. top level list item)
  • Comments start with a '#'

Runs playbooks with:

ansible-playbook [-b] [-e var1=val1 [...]] [--syntax-check] vsftpd.yml

Setting up nano

On freshly installed hosts, you usually only have nano (and vi). Which can be made better for YAML files:

cat << 'EOF' > $HOME/.nanorc
set autoindent
set tabsize 2
set tabstospaces
EOF

Playbook examples

vsftpd.yml

---
# The first level of elements are the plays themselves
- name: deploy vsftpd
  hosts: ansible2.example.com
  # Then the tasks are defined on how to complete this play
  tasks: 
  # Since this is a list of tasks, you put a dash first.
  - name: install vsftpd     # This is the text that is shown during the playbook run. Note that this comment is not shown
    yum: name=vsftpd         # yum in is the module. You can add arguments to the module on the same line, as in adhoc or spread them out on the next lines
  - name: enable vsftpd
    service: name=vsftpd enabled=true
  - name: create readme file
    copy:
      content: "welcome to this FTP server"
      dest: /var/ftp/pub/README
      force: no
      mode: 0444
...

facts.yml

---
- name: We're using facts!
  hosts: all
  become: true
  tasks: 
    - name: task1
      debug:
        msg: "doing task1"
    - name: task2
      debug:
        msg: "This is host {{ansible_hostname}}"
...

Set up repository

---
- name: Set up repo
  gather_facts: false
  hosts: localhost

  tasks:
    - name: Setting up
      yum_repository:
        name: mysql80-community
        description: MySQL 8.0 YUM Repo
        gpgcheck: 1
        enabled: 1
        baseurl: http://repo.mysql.com/yum/mysql-8.0-community/el/8/x86_64/
        gpgkey: http://repo.mysql.com/RPM-GPG-KEY-mysql
...

Do something on first number of UID

---
- name: Doing a regex test
  gather_facts: false
  hosts: localhost
  vars:
    - uid: 1234

  tasks:
    - name: A test
      debug:
        msg: "True"
      when: (uid | string)[0] | int == 1
...

Creating LVMs

---
- name: Using LVMs
  hosts: localhost
  gather_facts: no
  become: true

  tasks:
    - name: Partition /dev/sdb
      parted:
        device: /dev/sdb
        part_start: 0%
        part_end: 800MB
        number: 1
        flags: [ lvm ]
        state: present
    - name: Create Volume Group
      lvg:
        vg: vg_database
        pvs: /dev/sdb1
    - name: Create LV
      lvol:
        lv: lv_mysql
        vg: vg_database
        size: 512M
        shrink: false
    - name: Create XFS filesystem on lv1
      filesystem:
        dev: /dev/vg_database/lv_mysql
        fstype: xfs
    - name: Create mount point
      file:
        path: /mnt/mysql_backups
        state: directory
    - name: Mount lv1 to /data/lv1
      mount:
        path: /mnt/mysql_backups
        src: /dev/vg_database/lv_mysql
        fstype: xfs
        state: mounted
...

Note that if you do something with a VDO, that one needs a systemd startup script, not an fstab entry. So add to the 'mount' section:

opts: defaults,x-systemd.requires=vdo.service

These options can also be found in the VDO man page.

Creating an archive

---
- name: Backup /etc directory
  hosts: localhost
  become: true
  gather_facts: false

  tasks:
    - name: Archiving
      archive:
        path: /etc
        dest: /tmp/etc.tgz
        format: gz
...

Setting swappiness

- name: Set vm.swappiness to 5 in /etc/sysctl.conf
  sysctl:
    name: vm.swappiness
    value: '5'
    state: present

cron jobs

Install, enable and start service if needed. Think 'at/atd' or 'cronie/crond'

- name: backup users database tomorrow
  at:
    command: 'tar -czf /root/users.tgz /etc/passwd /etc/group /etc/shadow'
    count: 1
    units: days
    unique: true

or

- name: backup users database tomorrow
  cron:
    name: Backup Users
    hour: 5
    minute: 30
    weekday: 1-5
    user: root
    job: 'tar -czf /root/user.tgz /etc/passwd /etc/shadow'
    cron_file: user_backup

flowcontrol.yml

---
- name: Flow control
  hosts: all
  become: false
  gather_facts: false

  tasks: 
    - name: Loop
      debug:
        msg: "This is loop: {{ item }}"
      loop:
        - alpha
        - beta
        - gamma

    - name: Conditional
      debug:
        msg: "Variable hello is set to: {{ hello }}"
      when: hello is defined and hello != ''
...

Useful variables

  • group_names

List of groups the current host is part of when: "'webservers' in group_names"

  • when: (item.uid | string)[0] | int == 1

Get first number of uid

Docs:

Variables

---
- name: Storing results and testing them
  hosts: localhost
  gather_facts: false
  vars:
    - here: are
    - some: more
  vars_files:
    - vars/users
    - vars/groups

  tasks:
    - name: Register a variable
      shell: cat /etc/motd
      register: motd_contents

    - name: Use the variable in conditional statement
      debug: msg="motd contains the word in"
      when: motd_contents.stdout.find('in') != -1

    - name: More! More!
      debug: 
        msg: "here:{{here}} some:{{some}}"

    - name: var_files
      debug:
        msg: "user: {{item.name}} has {{item.password}} password"
      loop: "{{ users }}"
...

Handlers

Note that handlers only run once, and only when the tasks are completed

---
- name: Using a handler
  gather_facts: false
  hosts: localhost

  tasks:
    - name: Do something that has a non-ok state
      file:
        path: /tmp/123
        state: touch
      notify: result_handler


  handlers:
    - name: result_handler
      debug:
        msg: "Something happened!"
...

You can force handlers to run at a specific point, for example a reboot before you can continue with the other tasks. You do this via an:

- name: run handlers now
  meta: flush_handlers

Firewall

---
- name: Setting up the firewall
  gather_facts: false
  hosts: localhost

  tasks:
    - name: Firewall Package
      package:
        name: "{{ firewall_pkg }}"
        state: present
    - name: Firewall Service
      service:
        name: "{{ firewall_pkg }}"
        enabled: true
        state: started
    - name: UFW Ubuntu
      ufw:
        state: enabled
        policy: deny
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - "80"
        - "22"
      when: ansible_distribution == "Ubuntu"
    - name: Firewalld CentOS
      firewalld:
        service: "{{ item }}"
        permanent: true
        immediate: true
        state: enabled
      loop:
        - "http"
        - "ssh"
      when: ansible_distribution == "CentOS"
...

Set boot to graphical

Just softlink the right target to /etc/systemd/system/default.target

---
- name: set default boot target
  hosts: ansible2
  tasks:
    - name: set boot target to graphical
      file:
        src: /usr/lib/systemd/system/graphical.target
        dest: /etc/systemd/system/default.target
        state: link

Generating user passwords

You can use Python's function to hash a password:

# Leave the salt empty or omit it to let the function generate one for you
ansible localhost -m debug -a "msg={{ 'my_password' | password_hash('sha512', 'my_salt') }}"

This is equal to:

openssl passwd -6 -salt my_salt my_password

Vault

ansible-vault create more_vars.yml      # For newly created files. Will open an editor
ansible-vault encrypt more_vars.yml     # To encrypt already existing files

ansible-vault view more_vars.yml
ansible-vault edit more_vars.yml

ansible-playbook --ask-vault-pass some_script.yml                     # Will ask for a password
ansible-playbook --vault-password-file=passwd.txt some_script.yml     # Reads password from a file.. not really secure

Roles

Creating

  • Add 'roles_path = roles' to the ansible.cfg
ansible-galaxy role init roles/firewall
ansible-galaxy list
  • Now you edit 'roles/firewall/tasks/main.yml' and put the tasks in there
  • In the calling playbook you add the roll, which will run it automatically. The 'service_name' var is referenced in the role
---
- name: My playbook
  vars:
    - service_name: http
  roles:
    - firewall

Downloading

ansible-galaxy search php
ansible-galaxy search --author geerlingguy php
ansible-galaxy info geerlingguy.php
ansible-galaxy install geerlingguy.php
ansible-galaxy list

RHEL System Roles

RHEL System Roles are based on the community Linux System Roles and provide a uniform interface to make configuration tasks easier where significant differences may exist between versions of the managed operating system.

yum install rhel-system-roles

Also make sure you ansible.cfg has the default location for package installed roles included:

roles_path = roles:/usr/share/ansible/roles

To use them, you set the parameters in vars and then include the role:

vars:
    - selinux_policy: targeted
    - selinux_state: enforcing
  - include_role:
    name: rhel-system-roles.selinux

When looking for help: /usr/share/ansible/roles/rhel-system-roles.selinux/README.md Something simpler:

---
- hosts: ansible
  vars:
    timesync_ntp_servers:
      - hostname: pool.ntp.org
        iburst: yes
  roles:
    - rhel-system-roles.timesync

Doing it directly:

- name: Set httpd_can_network_connect flag on and keep it persistent across reboots
  seboolean:
    name: httpd_can_network_connect
    state: yes
    persistent: yes

Doing it via a system role:

---
- name: Enable the boolean httpd_can_network_connect 
 hosts: webserver
 become: yes
 vars:
   selinux_booleans:
     - name: httpd_can_network_connect
       state: on
       persistent: yes
 roles:
   - linux-system-roles.selinux

Commonly used facts

  • ansible_facts[‘distribution’]
  • ansible_facts[‘distribution_major_version’]
  • ansible_facts[‘os_family’]

Create your own facts

---
- hosts: database
  become: yes
 
  tasks:
    - name: Ensure the directory exists
      file:
        path: "/etc/ansible/facts.d/"
        state: directory
        recurse: yes
 
    - name: Copy content to file in new directory
      copy:
        content: "[sample_exam]\nserver_role=mysql\n"
        dest: "/etc/ansible/facts.d/custom.fact"
...

Links