diff options
-rw-r--r-- | README.md | 23 | ||||
-rwxr-xr-x | changepw.sh | 86 | ||||
-rw-r--r-- | changepw.yml | 71 | ||||
-rwxr-xr-x | getpwhash.py | 9 | ||||
-rw-r--r-- | inventory-changepw.yml.example | 12 | ||||
-rwxr-xr-x | prep.sh | 44 | ||||
-rw-r--r-- | ref/create_local_admin.txt | 139 |
7 files changed, 384 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..70b7b64 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Changepw +*Shell script that uses and aids ansible* +Use changepw to change a local user password on a set of hosts. + +These scripts work together and must be run in order. + +**prep.sh** makes a custom inventory file with all the hosts from the vcenter_matrix.csv list that can be ansible-pinged. If a system is unavailable for any reason, it will be excluded. +This script is not strictly necessary, but if you omit it you need to prepare the inventory-changepw.yml inventory file. + +**changepw.sh** has a hardcoded username in it but prompts for the new password and its confirmation. It saves those values to a vault file and then loops through the hard-coded sites and runs the ansible playbook that changes the local user password. + +### Usage + + cd /etc/ansible/shell/changepw + ./prep.sh + ./generate.sh + +### Dependencies +This tool depends on the output of the vcenter_matrix tool, so run it first. + +# Reference +## Weblinks +1. https://bgstack15.wordpress.com/2017/12/03/python-get-linux-compatible-password-hash/ diff --git a/changepw.sh b/changepw.sh new file mode 100755 index 0000000..dfb9f11 --- /dev/null +++ b/changepw.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# File: changepw.sh +# Location: /etc/ansible/shell/changepw/ +# Author: bgstack15@gmail.com +# Startdate: 2018-01-04 +# Title: Script that Executes the Password Change Across Listed Systems +# Purpose: Sets new password for local user across all systems in inventory, grouped by site +# History: +# Usage: +# Call prep.sh first, then changepw.sh +# Reference: +# Improve: +# Dependencies: +# vcenter_matrix/generate.sh +# Documentation: +# Run from the ansible control host, as an account that can ssh in and root up. +# This will hardcore modify the /etc/shadow file, which will trigger AIDE. + +# FUNCTION +clean_changepw() { + rm -rf "${tmpdir}" 1>/dev/null 2>&1 +} + +# TEMP FILES +tmpdir="$( mktemp -d )" +trap 'clean_changepw ; trap "" 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ; exit 0 ;' 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 +vaultfile="$( TMPDIR="${tmpdir}" mktemp )" +pwfile="$( TMPDIR="${tmpdir}" mktemp )" + +# GET USER-PROVIDED VALUES +tl="${1}" # this limit of hosts in the inventory +test -z "${tl}" && tl="all" +tu=bgstack15 +printf "%s" 'New password: ' +read -s pw1 +printf '\n' +printf "%s" 'New password (again): ' +read -s pw2 +printf '\n' + +# DEFINE VALUES +td=/etc/ansible/shell/changepw +playbook="${td}/changepw.yml" +inv="${td}/inventory-changepw.yml" +logfile="${td}/log/changepw.$( date "+%Y-%m-%d-%H%M%S" ).log" +py_getpwhash="${td}/getpwhash.py" + +# VALIDATE VALUES +if test "${pw1}" != "${pw2}"; +then + echo "${0}: Passwords do not match. Aborted." + exit 1 +fi +pwhash="$( /bin/python "${py_getpwhash}" "${pw1}" )" +if ! mkdir -p "$( dirname "${logfile}" )" ; then echo "${0}: Need write access to directory of logfile \"${logfile}\". Aborted." 1>&2 && exit 1 ; fi +if ! touch "${logfile}" ; then echo "${0}: Need write access to logfile \"${logfile}\". Aborted." 1>&2 && exit 1 ; fi + +# PREPARE VAULT FILE +echo -e "thispassword: ${pw1}" > "${vaultfile}" +echo -e "thispasswordhash: ${pwhash}" > "${vaultfile}" +echo "thisuser: ${tu}" >> "${vaultfile}" +echo "$( pwmake 300 )" > "${pwfile}" +ansible-vault encrypt "${vaultfile}" --vault-password-file "${pwfile}" 2>&1 | grep -viE 'encryption successful' +unset pw1 pw2 + +# MAIN LOOP +{ + echo "limit=${tl}" + for ts in preprod prod ; # thissite + do + echo "---------- ${ts}" | tr '[[:lower:]]' '[[:upper:]]' + # for maintenance: --skip-tags 'expect,changepw' + + # USE ONE OF THE TWO FOLLOWING PLAYBOOK STATEMENTS + + ## Use the password hash, so we do not have to use the pexpect package + time unbuffer ansible-playbook "${td}/changepw.yml" -i "${inv}" --become -u ansible_${ts} -l "${tl}" --vault-password-file "${pwfile}" -e "vaultfile=${vaultfile}" -e "sitelimit=${ts}" -v --skip-tags 'expect' + + ## Use pexpect, which requires the yum package + #time unbuffer ansible-playbook "${td}/changepw.yml" -i "${inv}" --become -u ansible_${ts} -l "${tl}" --vault-password-file "${pwfile}" -e "vaultfile=${vaultfile}" -e "sitelimit=${ts}" -v --skip-tags 'hardcore' + + done +} 2>&1 | tee -a "${logfile}" + +# EXIT CLEANLY +exit 0 diff --git a/changepw.yml b/changepw.yml new file mode 100644 index 0000000..eafa847 --- /dev/null +++ b/changepw.yml @@ -0,0 +1,71 @@ +--- +# File: changepw.yml +# Location: /etc/ansible/shell/changepw/ +# Author: bgstack15@gmail.com +# Startdate: 2018-01-04 +# Title: Ansible Playbook that Changes My Password +# Purpose: Make changing my password easy in an environment where hosts have expirable passwords +# History: +# Usage: +# Use changepw.sh, which calls this playbook. +# Reference: +# ref/create_local_admin.yml +# Improve: +# Document: + +- name: Playbook that changes my password + vars_files: + - "{{ vaultfile }}" + hosts: "{{ sitelimit }}" + tasks: + - ping: + + - name: Install dependencies on OL7 + yum: + name: "{{ item }}" + enablerepo: ol7_latest + with_items: + - pexpect + when: + - ansible_distribution_major_version == "7" + - ansible_os_family == "RedHat" + tags: + - expect + + - name: Learn if local user exists + shell: grep -o -e "^{{ thisuser }}:" /etc/passwd | cat - + register: user_stat + changed_when: false + + - name: Set password only when local user exists + block: + + - name: Set permanent password + expect: + command: passwd "{{ thisuser }}" + responses: + (?i)password: "{{ thispassword }}" + tags: + - expect + + - name: Set password, hardcore mode + lineinfile: + path: /etc/shadow + regexp: '^({{ thisuser }}:)\$.{80,120}((:.+){6})' + backrefs: yes + line: '\1{{ thispasswordhash }}\2' + backup: yes + register: shadow + tags: + - hardcore + + - name: Set password last date set to today + shell: chage -d "{{ ansible_date_time.date }}" "{{ thisuser }}" + changed_when: false + tags: + - hardcore + + when: + - user_stat.stdout != "" + tags: + - changepw diff --git a/getpwhash.py b/getpwhash.py new file mode 100755 index 0000000..91bb4a6 --- /dev/null +++ b/getpwhash.py @@ -0,0 +1,9 @@ +#!/bin/python +# Reference: +# https://bgstack15.wordpress.com/2017/12/03/python-get-linux-compatible-password-hash/ +import crypt, getpass, sys; +if len(sys.argv) >= 2: + thisraw=str(sys.argv[1]); +else: + thisraw=getpass.getpass(prompt='New password: ') +print(crypt.crypt(thisraw,crypt.mksalt(crypt.METHOD_SHA512))) diff --git a/inventory-changepw.yml.example b/inventory-changepw.yml.example new file mode 100644 index 0000000..9c3179a --- /dev/null +++ b/inventory-changepw.yml.example @@ -0,0 +1,12 @@ +[prod] +prod1 +prod2 +prod3 + +[preprod] +dev1 +dev2 +dev3 +test1 +test2 +test3 @@ -0,0 +1,44 @@ +#!/bin/sh +# File: prep.sh +# Location: /etc/ansible/shell/changepw/ +# Author: bgstack15@gmail.com +# Startdate: 2018-01-04 +# Title: Script that Prepares the Inventory List for Changepw +# Purpose: Trims out the hosts that are not suitable for the password change, or unreachable +# History: +# Usage: +# Run this before the changepw.sh script. +# Reference: +# Improve: +# Documentation: +# Dependencies: +# vcenter_matrix + +td=/etc/ansible/shell/changepw/ +tf="${td}/inventory-changepw.yml" +tfailed="${td}/log/unreachable.$( date "+%Y-%m-%d" ).log" +vcenter_matrix_file=/etc/ansible/shell/vcenter_matrix/vcenter_matrix.csv + +# DEPENDENCIES +if ! touch "${tf}" ; then echo "${0}: Need write access to file \"${tf}\". Aborted." 1>&2 && exit 1 ; fi +chmod 0660 "${tf}"; +if ! test -r "${vcenter_matrix_file}" ; then echo "${0}: Ensure vcenter list file \"${vcenter_matrix_file}\" is readable. Aborted." 1>&2 && exit 1 ; fi +if ! touch "${tfailed}" ; then echo "${0}: Need write access to file \"${tfailed}\". Aborted." 1>&2 && exit 1; fi + +# FETCH ALL VIRTUAL MACHINES +thisinput="$( cut -d',' -f2 "${vcenter_matrix_file}" | sed -r -e 's/\.prod1\.example\.com//;' | sort )" +{ + echo "[prod]" + echo "${thisinput}" | grep -E '1[0-9]{2}$' + echo "" + echo "[preprod]" + echo "${thisinput}" | grep -E '2[0-9]{2}$' + +} > "${td}/inventory-changepw.yml" + +# REMOVE UNREACHABLE ONES +cat /dev/null > "${tfailed}" +ansible -i "${tf}" prod -u ansible_prod -m ping | grep -E '=>' | awk '!/SUCCESS/{print $1}' >> "${tfailed}" +ansible -i "${tf}" preprod -u ansible_preprod -m ping | grep -E '=>' | awk '!/SUCCESS/{print $1}' >> "${tfailed}" +grep -vE -f "${tfailed}" "${tf}" > "${tf}.$$" +/bin/mv "${tf}.$$" "${tf}" diff --git a/ref/create_local_admin.txt b/ref/create_local_admin.txt new file mode 100644 index 0000000..81eb918 --- /dev/null +++ b/ref/create_local_admin.txt @@ -0,0 +1,139 @@ +--- +# File: /etc/ansible/books/create_local_admin.yml +# Author: bgstack15 +# Startdate: 2017-09-12 +# Title: Playbook that creates a local user that is an admin +# Purpose: Makes it easier to deploy local admins with special account restrictions. +# History: +# 2017-11-28 commented out /home/ansible/mypassword. Added vars_prompt and Install libselinux-python +# Usage: +# time ansible-playbook /etc/ansible/books/create_local_admin.yml -i /etc/ansible/inv/inv.yml --become -u ansible_preprod -e 'host=test1,test2,test3' -e 'item=bgstack15' -e 'uid=9985' -e 'ssh_key=true' -v | ctee /etc/ansible/log/bgstack15.log +# Must run as root, so use the --become flag! +# Required vars: item,uid +# Optional vars: ssh_key=true. If ssh_key is true, will copy in authorized key from ansible server /home/{{ item }}/.ssh/id_rsa.pub +# Make file /root/mypassword with the contents: +# --- +# password:D31ic10u$fu77yc@T +# ... +# Reference: +# Version: 2017-11-28a +# Notes: +# The specific choice to use lots of command modules instead of built in user and group modules is because we have sssd users in some cases, which are being resolved, but this script exists because we definitely want to create local users. The convention is to use the exact same name for local users, even though they have the same home directory as the sssd users. The uid collisions will only affect those with local users, which will really only be us admins. + +- hosts: "{{ host }}" +# vars_files: +# - /home/ansible/mypassword + vars_prompt: + - name: "password" + prompt: "Enter Password" + private: yes + encrypt: "sha512_crypt" + confirm: yes + salt_size: 7 + tasks: + - name: Install libselinux-python + yum: name=libselinux-python + - name: determine if local user exists + command: grep -o -e "^{{ item }}:" /etc/passwd + register: user_stat + ignore_errors: true + changed_when: false + + #- debug: + # var: user_stat.stdout + + - name: create local user when local user is absent + block: + + - name: create local group + command: groupadd -g {{ uid }} "{{ item }}" + + - name: create local user + command: luseradd -g {{ uid }} -u {{ uid }} "{{ item }}" + + when: user_stat.stdout == "" + + - name: ensure admin user is in wheel + user: + name: "{{ item }}" + append: yes + groups: wheel + + - name: Move pexpect-3.3 to server and untar + unarchive: + src: /etc/ansible/templates/pexpect-3.3.tar.gz + dest: /usr/ + owner: root + group: root + mode: 0770 + register: pexpect_installed + + - name: Install pexpect + command: /usr/bin/python setup.py install + args: + chdir: /usr/pexpect-3.3/ + + - name: Set password to permanent password + expect: + command: passwd "{{ item }}" + responses: + (?i)password: "{{ password }}" + + - name: Password last set on today, with minimum password life of 0 days + command: chage -d "{{ ansible_date_time.date }}" -m 0 -E 99999 -M -1 "{{ item }}" + + #- name: Set expiration date of effectively never + # command: usermod -e 99999 "{{ item }}" + + - name: get contents of public key + command: printf "{{ lookup('file','/home/{{ item }}/.ssh/id_rsa.pub') }}\n" + register: contents + changed_when: false + when: ssh_key is defined + + - name: add ssh key for user, from controlling server + block: + #- debug: + # var: contents.stdout + + - name: check if authorized_keys file exists already + stat: + path: /home/{{ item }}/.ssh/authorized_keys + register: authorized_keys + changed_when: False + + - debug: var=authorized_keys + + - name: check authorized_keys for key already + command: grep "{{ contents.stdout }}" /home/{{ item }}/.ssh/authorized_keys + register: check_authorized_keys + changed_when: False + ignore_errors: true + when: authorized_keys.stat.exists|bool == true + + - debug: var=check_authorized_keys + + - name: prepare user .ssh directory + file: + path: /home/{{ item }}/.ssh + recurse: yes + state: directory + owner: "{{ item }}" + group: "{{ item }}" + mode: 0700 + when: (check_authorized_keys.stdout is defined and check_authorized_keys.stdout == "") or (authorized_keys.stat.exists|bool == false) + + - name: place ssh key for user + lineinfile: + path: /home/{{ item }}/.ssh/authorized_keys + state: present + line: "{{ contents.stdout }}" + create: yes + owner: "{{ item }}" + group: "{{ item }}" + mode: 0600 + when: (check_authorized_keys.stdout is defined and check_authorized_keys.stdout == "") or (authorized_keys.stat.exists|bool == false) + + when: ssh_key is defined and ssh_key|bool == true and contents.stdout != "" and contents.stdout != "\n" + +... |