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

  1. Version:  not tracked
  2. 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
# -- 

 

 

 

Updated Aug 01, 2023
Version 2.0
  • Thanks 424694, a Kudos is a good way to mark this content as useful for others.  I'm glad you found it useful.  Thanks.