Introduction
These python scripts do the following:
init_vault.py
, initialized the vault and create an encryption key. The initialization creates 5 keys and 1 root token for unsealing and login the vault respectively.get_vault_resuly.py
, this script gets the keys and token to unseal and login the vault.vault_mgmt.py
, this script gets the key and value pair stored in kv/cisco, the key and value pair contains the username and password for the routers.
run_ansible.py
, this script runs theansible-playbook -i hosts backup.yaml -u cyruslab -k
command.
The fourth point is actually not I wanted to do, what I wanted is to use the ansible
python module, however there is no good documentation or examples for running the yaml file with ansible-playbook, if you have a good guide please leave message to the comment.
hosts file
Initially I got below error, my original hosts file only contains the ip addresses however i need to actually put in the network_os.
To solve the above problem you need to put the ansible_network_os
in the hosts file.
[ios] 192.168.1.30 192.168.1.31 192.168.1.32 [ios:vars] ansible_network_os=ios
ansible.cfg
You need to uncomment the host_key_checking
in the ansible.cfg
this is because by default ansible performs strict host key checking you will encounter error in connecting to the routers if this is not uncommented
Yaml file
--- - name: Cisco ios devices hosts: ios gather_facts: no connection: network_cli tasks: - name: show version for cisco routers ios_command: commands: - show running-config - show version register: cisco_config - name: Save the config copy: content: "{{ cisco_config.stdout[0] }}" dest: "/home/cyruslab/router_config/{{ inventory_hostname }}_backup.txt" - name: Get show version copy: content: "{{ cisco_config.stdout[1] }}" dest: "/home/cyruslab/router_config/{{ inventory_hostname }}_show_versions.txt" ...
register
is like a variable, for each command executed it will be put into the stdout
as a list object, stdout[0]>
contains the output of show run
, stdout[1]
contains the output of show version
.
This is I am using the ios_command
this is not used for configuration.
---
is the start of the yaml, ...
is the end of the yaml file.
For each hyphen is an indication that it is a list, in json it is like this:
[ { "name": "Cisco ios devices", "hosts": "ios", "gather_facts": "no", "connection": "network_cli", "tasks": [ { "name": "show version for cisco routers", "ios_command": { "commands": [ "show running-config", "show version" ] }, "register": "cisco_config" }, { "name": "Save the config", "copy": { "content": "{{ cisco_config.stdout[0] }}", "dest": "/home/cyruslab/router_config/{{ inventory_hostname }}_backup.txt" } }, { "name": "Get show version", "copy": { "content": "{{ cisco_config.stdout[1] }}", "dest": "/home/cyruslab/router_config/{{ inventory_hostname }}_show_versions.txt" } } ] } ]
init_vault.py
If the hashicorp vault is newly installed and no keys and root token, run this first.
from hvac import Client from os.path import exists from cryptography.fernet import Fernet import json ''' hvac is the python module for Hashicorp vault api Because after initialization the vault returns a dictionary and dictionary does not have encode method hence i json is used to convert the dictionary into a string by using the dumps method. The string then needs to be encoded into utf-8 as bytes, then encrypt the data in bytes. ''' # Client class parameter verify=False because the ssl cert cannot # validate the ip address, there is no hostname vault.cyruslab.local yet. client = Client(url='https://127.0.0.1:8200', verify=False) # if the vault has not been initialized, initialize now. if not client.is_initialized(): shares = 5 # generate 5 key shares threshold = 3 # require 3 keys to unseal the vault. result = client.sys.initialize(shares, threshold) # if symmetric key is not available generate one. # save the key as a file for future use. if not exists('enc.key'): key = Fernet.generate_key() with open('enc.key', 'wb') as file: file.write(key) # use the symmetric key with open('enc.key', 'rb') as file: sym_key = file.read() fernet = Fernet(sym_key) # encrypt the result delivered by initialization. cipher_result = fernet.encrypt(json.dumps(result).encode('utf-8')) # save the result into a file. with open('result.enc', 'wb') as file: file.write(cipher_result)
get_vault_result.py
The purpose is not to run this directly, but to use by vault_mgmt.py
script.
from cryptography.fernet import Fernet from json import loads with open('result.enc', 'rb') as file: cipher_result = file.read() with open('enc.key', 'rb') as file: key = file.read() fernet = Fernet(key) plaintext_result = (fernet.decrypt(cipher_result).decode('utf-8')) result_in_json = loads(plaintext_result) # store the first 3 keys keys = result_in_json.get('keys')[0:3] root_token = result_in_json.get('root_token')
vault_mgmt.py
This script is also not to be run directly but used by run_ansible.py
to retrieve username and password.
from hvac import Client # The results got from the result.enc from get_vault_result import root_token, keys # Create a vault client, dun check the tls cert # define the vault address. # put in the root token client = Client(url='https://127.0.0.1:8200', verify=False, token=root_token) # is_sealed() will be deprecated, use sys.is_sealed() instead. # if the vault is sealed. if client.sys.is_sealed(): # submit all stored keys # first 3 keys from the 5 are stored in keys. client.sys.submit_unseal_keys(keys) # get the username and password dict creds = client.secrets.kv.v2.read_secret_version( path='cisco', # not documented properly in the doc, you need to explicitly define the mount point. mount_point='kv' ).get('data').get('data') # seal the vault client.sys.seal()
run_ansible.py
The actual script that will execute the backup.yaml
file.
This script runs like a human using the command line ansible-playbook
though it works, but this is not what i want.
from vault_mgmt import creds import pexpect # run the command of ansible-playbook. # not ideal because i want to use ansible module to run it. # if you know any examples please let me know in comment. child = pexpect.spawn('ansible-playbook -i hosts backup.yaml -u {} -k'.format(creds.get('username'))) # this is the expected prompt child.expect('SSH password:') # after the expected prompt put in the password. child.sendline('{}'.format(creds.get('password'))) # wait until the command finished. child.expect(pexpect.EOF) # get the output after command finished. # child.before is in bytes, need to decode into string. print(child.before.decode('utf-8'))