ansible use jump box

If you need to connect through an intermediate jump box, or bastion server, here’s how you configure the inventory file:

[other-lan]
c7-prod-app-01 
[other-lan:vars]
ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p -q ansible_user@jumpbox.otherlan.example.com"'

If the jump box can resolve the target name as is, you don’t need to specify the IP address. However, you can also force a specific IP address.

c7-prod-app-01 ansible_host=10.300.15.3

References

Weblinks

Shamelessly ripped from Ansible with a bastion host / jump box? [stackoverflow.com]

Ansible disable service fails if service is not defined

If you are trying to disable and stop a system service, and it fails out when the service is not defined, you probably just want to continue.

- name: disable service
  service:
    name: chronyd
    enabled: no
    state: stopped
  register: disable_service
  failed_when: "disable_service is failed and ('Could not find the requested service' not in disable_service.msg)"

Credit goes to Rebekah Hayes.

Ansible check if list is in list

Here’s my hack for how to check if a list’s items are all in another list.

  - name: learn current mounts
    shell: mount | grep -iE "cifs|nfs" | grep -viE '^\s*#|pipefs|proc\/fs\/nfsd' | awk '{print $3}' | sort
    args:
      warn: no
    register: mounts

  - name: learn requested mounts
    shell: grep -iE "cifs|nfs" /etc/fstab | grep -viE '^\s*#|pipefs|proc\/fs\/nfsd' | awk '{print $2}' | sort
    args:
      warn: no
    register: fstab_entries

  - name: fail when fstab_entries not in mounts
    debug:
      msg: "{{ item }}"
    failed_when:
    - 'item not in mounts.stdout_lines'
    loop: "{{ fstab_entries.stdout_lines | flatten(levels=1) }}"

I am just using a debug task that loops over all the items of the possibly shorter list, with a condition to fail when the item is not in the longer list.

Ansible find first accessible proxy and use it

If you need to find the first available http proxy and use it for a process, you can use a python snippet to discover it and use it.

https://gitlab.com/bgstack15/former-gists/tree/master/get_first_open_port.py

vars:
  http_proxies:
  - 192.168.1.5:3128
  - proxy5.internal.example.com:3128

tasks:

  - name: learn which proxy to use
    script: get_first_open_port.py {{ http_proxies | join( " " ) }}
    changed_when: false
    register: open_ports

  - set_fact:
      http_proxy: "{{ open_ports.stdout_lines[0] }}"
    when:
    - 'open_ports.stdout | length > 0'
    failed_when:
    - 'open_ports.stdout | length = 0'

  - name: use http_proxy environment variable
    script: script_needing_internet.sh -i {{ inputvar }}
    environment:
      http_proxy: "http://{{ http_proxy | default(omit) }}"

The sole output is the first hostname and port available.

#!/usr/bin/python2
# Filename: get_first_open_port.py
# Location: /etc/ansible/roles/install_sccm/files/
# Author: bgstack15
# Startdate: 2018-10-02 10:13
# Title: Script that Gets the First Open Port
# Purpose: Return to standard output the first valid host and port to use as a http proxy
# Project: projects derived from ansible role certreq
# History:
# Usage:
#    in ansible
# Reference:
#    https://stackoverflow.com/questions/19196105/python-how-to-check-if-a-network-port-is-open-on-linux
#    string split https://stackoverflow.com/questions/6670290/split-string-into-different-variables-instead-of-array-in-python
# Improve:
# Documentation:

import socket, sys
from contextlib import closing

GFOP_VERSION="2018-10-02a"

def check_socket(host, port):
   with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
      sock.settimeout(2)
      if sock.connect_ex((host,port)) == 0:
         print host + ":" + str(port)
         return True
   return False


x = 0
for myarg in sys.argv:

   # show version
   if myarg == "--version" or myarg == "-V":
      print(sys.argv[0]+" "+GFOP_VERSION)
      sys.exit(0)

   x = x + 1
   # skip the script $0 name itself
   if x > 1:
      # split on the colon
      host, port = myarg.split(":",2)
      # short-circuit upon first successful one
      if check_socket(host,int(port)):
         sys.exit(0)

Control ansible verbosity and silence with tags

I wonder if I have stumbled upon a use case that other haven’t yet.

I just learned about how to use callback plugins in ansible, which are generally responsible for output.

I found one named “selective” which means that it will show the output for only the tasks with a tag titled “print_action.” My goals included having mundane tasks be silent, not whitelisting the important tasks. Plus, it shows every task anyway if you do a -v for verbose.

Also, using callbacks took me some effort. To even allow different callback plugins, you have to add the names you want available to the [defaults] callback_whitelist variable in ansible.cfg, which is comma-space-delimited. And place the plugin in of the directories specified by callback_plugins, normally /usr/share/ansible_plugins/callback_plugins.

Next, to actually use it for a play, define ANSIBLE_STDOUT_CALLBACK with the name of the plugin to use.

So:

$ cat ansible.cfg
[default]
... OUTPUT TRUNCATED ...
callback_whitelist = short
$ ANSIBLE_STDOUT_CALLBACK=short ansible-playbook myplaybook.yml

I present to you today my customized callback plugin! It operates just like the default callback, but with two modifications, both dependent on tags of the tasks.

  • tag: verbose will cause the output to always be like the -v flag
  • tag: silent will completely hide the task output when successful. Failures and skips appear like normal.

My plays tend to have a lot of minor tasks like deploying assistant scripts or cleaning up temp files, and I don’t want tons of scrollback in my terminal just because 5 different temp files were removed (or not) at the end of a play across 150 systems.

Verify ansible vault password

# prepare vault password file
printf 'Vault password: ' ; read -se VAULT_PASS ; printf '\n' ;
echo "${VAULT_PASS}" > "${PWFILE}"
# fail out if password is incorrect
! ansible-vault view --vault-password-file "${PWFILE}" "${VAULTFILE}" 1>/dev/null && exit 1

You can use shell to read in the password and save it to a file. Just remember to clean it up at the end!

I like to do this right before a shell loop that calls ansible with vaulted values multiple times, so I’m not prompted multiple times to enter the password.

ansible control hosts entries

Somebody should make a module of this. In the meantime, here’s a simple hosts entries task in ansible.

- name: hosts entries
  lineinfile:
    path: "{{ etc_hosts_file | default('/etc/hosts') }}"
    regexp: '^\s*{{ item.ip }}.*'
    line: "{{ item.ip }}   {{ item.hostnames | sort() | join(' ') }}"
    backup: yes
  with_items:
  - "{{ hosts_entries }}"

Use a list like so:

hosts_entries:
- { ip: '1.2.3.4', hostnames: ['example.com','www.example.com'] }
- { ip: '5.6.7.8', hostnames: ['ldap.example.com'] }

Playbook that Converts Local to AD Users

If you like removing local users in favor of the domain users, check out how to do that in shell at my post Convert Local to AD Users.

If you want to do it at scale, you can wrap it with a bit of ansible. Check out the full thing with syntax highlighting on gitlab: https://gitlab.com/bgstack15/former-gists/blob/master/cladu.yml/cladu.yml

---
# Only use one thisuser at a time, for the fail/changed logic to work correctly!
# usage: ansible-playbook -l targethost1 /etc/ansible/books/stable/cladu.yml -v -e 'thisuser=joneill'

- name: book that runs cladu
  hosts: all
  become: yes
  become_user: root
  become_method: sudo
  tasks:

  - name: copy in rpm
    copy:
      src: /etc/ansible/files/bgscripts-core-1.3-9.noarch.rpm
      dest: /tmp/
      mode: 0644

  - shell: rpm -U --nodeps /tmp/bgscripts-core-1.3-9.noarch.rpm
    args:
      warn: no
    register: this_rpm
    failed_when:
    - 'not ("is already installed" in this_rpm.stdout or "is already installed" in this_rpm.stderr or this_rpm.rc == 0)'
    changed_when:
    - 'not ("is already installed" in this_rpm.stdout or "is already installed" in this_rpm.stderr)'

  - shell: /usr/share/bgscripts/work/cladu.sh -r -g '{{ thisuser }}'
    args:
      warn: no
    register: this_shell
    changed_when:
    - 'not ("Skipped" in this_shell.stdout or "Failed" in this_shell.stdout)'
    failed_when:
    - '"Failed" in this_shell.stdout'

Playbook that resets user password

As an admin, it’s my job to reset user passwords who still use local accounts. I’m working on converting users to domain accounts, but in the mean time, here’s my little book.
Check it out with context highlighting at https://gitlab.com/bgstack15/former-gists/blob/master/reset-password.yml/reset-password.yml

---
# Dependencies: pwhashgen.py from bgscripts-core, installed on ansible server
- name: book that resets password for thisuser
  hosts: all
  become: yes
  become_user: root
  become_method: sudo
  tasks:

  - name: generate pw hash
    shell: /usr/share/bgscripts/py/pwhashgen.py "{{ thispw | default('TEMP_PASSWORD_HERE') }}"
    register: thispw
    delegate_to: localhost
    changed_when: false
    run_once: true

  - user:
      name: "{{ thisuser }}"
      password: "{{ thispw.stdout }}"

  - shell: passwd -e "{{ thisuser }}"
    args:
      warn: no

Playbook that runs du

Here’s a playbook I wrote for my Ops team. Apparently they’re impressed by the simplest things.

---
# Filename: du.yml
# Author: bgstack15
# Startdate: 2018-07-24
# Title: Book that Runs du
# Purpose: Run du with elevated privileges, primarily for auditing /home
# History:
# Usage:
#    In ansible tower.
#    Variables:
#       path=/home   What location to check
#       max_depth=1  How many directories to read in to
#       tail=5       How many to show
# Reference:
# Improve:
# Documentation:

- name: book that runs du
  hosts: all
  become: yes
  become_user: root
  become_method: sudo
  tasks:

  - name: du
    shell: /usr/bin/du -xBM --max-depth "{{ max_depth | default('1') }}" "{{ path | default('/home') }}" | sort -n | tail -n "{{ tail | default('5') }}" | sed -r -e 's/[[:space:]]+/ /g;'
    args:
      warn: no
    register: du

  - debug:
      msg: "{{ du.stdout_lines }}"