Ansible module to update BIG-IP root/admin passwords
Code is community submitted, community supported, and recognized as ‘Use At Your Own Risk’.
Short Description
Have a large environment with password rotation requirements? Tired of trying a long list of old passwords to get into an old box? If you have been working with F5 for a while and in a larger environment, the answer to those questions is probably YES.
This Ansible module aims to provide an automated solution to update all your bigip root/admin passwords to ensure compliance, security and really, peace of mind.
Problem solved by this Code Snippet
I have been in many scenarios where root/admin password management is not easy, especially when working in bigger environments.
As time goes on, making sure all the root/admin passwords are up to date and easily changable can become a very time consuming task.
This ansible playbook will loop through every bigip in the inventory, utilize a list of old/known passwords to gain access and update the root/admin passwords as specified.
This method has saved me a ton of time over the years.
How to use this Code Snippet
This is a custom Ansible module, so it is mainly a python script. I've also added an example use. Add the python script to the appropriate Ansible folder and reference in a playbook
Supporting Documentation
https://github.com/DumpySquare/portable_ansible_env/blob/master/playbooks/dev/f5_fix_pass.yml
https://github.com/DumpySquare/portable_ansible_env/blob/master/playbooks/dev/library/f5_fix_pass.py
https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html
https://docs.ansible.com/ansible/latest/dev_guide/developing_modules.html
https://learning-ocean.com/tutorials/ansible/ansible-custom-module
https://medium.com/codex/how-to-write-an-ansible-module-part-1-3d93bfd4dd7e
https://blog.toast38coza.me/custom-ansible-module-hello-world/
Code Snippet Meta Information
- Version: not tracked
- Coding Language: python
Full Code Snippet
#!/usr/bin/python
import re
import paramiko
from ansible.module_utils.basic import *
def main():
output = [] # create an empty list to log all out output
# define the fields coming from the ansible module
fields = {
"bigip": {"default": True, "type": "str"},
"local_user": {"default": True, "choices": ['admin', 'root'], "type": "str"},
"new_password": {"default": True, "no_log": True, "type": "str"},
"old_password": {"default": True, "no_log": True, "type": "list"}
}
# pulls in the "fields" above to be used as a dict
module = AnsibleModule(argument_spec=fields)
i = 0 # counter for number of old passwords
# assign module params as local variables - makes them easier to use/change
bigip = module.params['bigip']
uname = module.params['local_user']
passwds = module.params['old_password']
newpass = module.params['new_password']
while True:
log = "Trying to connect to %s as %s with %s (%i/%i)" % (bigip,uname, passwds[i], i, len(passwds))
output.append(log) # broke into two lines to clean things up
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(bigip, username=uname, password=passwds[i], look_for_keys=False, allow_agent=False)
output.append(" ~~~~~~~~~~ Connected ~~~~~~~~~~ ")
output.append(" Connected to %s as %s with %s" % (bigip, uname, passwds[i]))
break
except paramiko.AuthenticationException:
i += 1 # increment our password counter for this attempt
if i == len(passwds):
# no passwords worked - quit with fail
module.fail_json(msg="No root/admin passwords worked...")
setadmin = "tmsh modify auth user admin password %s" % newpass
output.append(" -- Executing: %s" % setadmin)
stdin, stdout, stderr = ssh.exec_command(setadmin)
output.append(stdout.read())
setroot = "echo -e \"%s\\n%s\" | tmsh modify auth password root" % (newpass, newpass)
output.append(" -- Executing: %s" % setroot)
stdin, stdout, stderr = ssh.exec_command(setroot)
output.append(stdout.read())
output.append(" -- Saving config: tmsh save sys config -- ")
stdin, stdout, stderr = ssh.exec_command("tmsh save sys config")
output.append(stdout.read())
ssh.close # close ssh session
module.exit_json(change=True, response=output)
if __name__ == '__main__':
main()
Example usage
---
- hosts: localhost
connection: local
gather_facts: no
vars_prompt:
- name: "dest"
prompt: "hostname(inv-fqdn/IP) to update"
private: no
#default: "192.168.1.5" # only used for testing
tasks:
- name: fix root/admin passwords
f5_fix_pass:
bigip: "{{ dest }}"
local_user: "root" # root or admin
new_password: "{{ latest_passwd }}" # single string
old_password: "{{ old_passl }}" # can be a list
# - pass1
# - pass2
# - pass2
register: result
- debug: var=result
## future enahancements:
# add remote user like radius/tacacs to try first, so we don't have to brut force our way in
# - utilize the "provider" dict like other F5 modules
# - this would also require checking if in tmsh shell and/or getting there if needed
# -- it would require different commands if in tmsh or not
# update the "fields" dict from play definition to "argument_spec" to be inline with f5 standards
# update script output and hide passwords - DONE
# allow script to use rsa keys...?
# - test no passwords working - DONE - set module fail_json with error message
# - test no network connectivity - HALF DONE
# -- if no IP, it won't resolve and the socket fails
# --