Ansible-Access Network via Telnet

Posted by Blog - uTIcARdI on September 4, 2018

I’m learning Ansible by reading the official document and some books like Ansible入门. Most of the instructions show you how to deploy or maintenance the services on Linux, or file operations. But what I do is trying to explore its capability on network device management automation.

The most import thing to practice routers/switches/routing protocol is accessing the device and configure it via CLI when I started to learn network, so I start my ansible tour from accessing the network device from CLI too.

I build a simple lab on EVE for testing: a Cios IOS switch for telnet access, a Cisco IOS router for SSH access.

Topo
Topo

then install Python, ansible on macOS High Sierra (it should be same on Linux, windows), here is the version info:

ansible 2.6.3
config file = None
configured module search path = ['/Users/kz/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /Users/kz/Documents/C/ansibleprj/venv/lib/python3.7/site-packages/ansible
executable location = /Users/kz/Documents/C/ansibleprj/venv/bin/ansible
python version = 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24) [Clang 6.0 (clang-600.0.57)]

Ansible is designed to running over SSH, but there is a module: telnet extends its capbility to access network device via Telnet.

Part 1

1. Enable Telnet on device

start device: SW on eve, configure IP, enable telnet from line vty, set username/password: test/test123

2. new inventory_telnet.yml
---

telnet:
hosts:
    sw1:
    ansible_host: "192.168.1.100"

Note: I use YAML format though ini format should also work because Misleading documentation, it shows that you can store vaulted passwords in INI files but it does not work #43897

3. new playbook: pb_telnet.yml
---

- name: show version
  hosts: telnet
  gather_facts: false
  connection: local
  tasks:
    - name: telnet sw
      telnet:
        user: test
        password: test123
        login_prompt: "Username: "
        prompts:
          - '[>|#]'
        command:
          - terminal length 0
          - show version

The playbook will connect to the device: sw, and run command show version

4.run playbook

Run the task by the command:

ansible-playbook -i inventory_telnet.yml pb_telnet.yml
5. Debugging

Unfortunately, the task failed to run:

(venv) KZs-MacBook-Pro:pys kz$ ansible-playbook -i inventory_telnet.yml pb_telnet.yml
PLAY [show version] ***********************************************************************************************************************************
TASK [telnet sw] **************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: argument should be integer or bytes-like object, not 'AnsibleUnicode'
fatal: [sw1]: FAILED! => {"msg": "Unexpected failure during module execution.", "stdout": ""}
to retry, use: --limit @/Users/kz/Documents/C/ansibleprj/pys/pb_telnet.retry
PLAY RECAP ********************************************************************************************************************************************
sw1 : ok=0 changed=0 unreachable=0 failed=1

Debugging it by the command:

ansible-playbook -i inventory_telnet.yml pb_telnet.yml -vvv

get the full stack:

task path: /Users/kz/Documents/C/ansibleprj/pys/pb_telnet.yml:8
The full traceback is:
Traceback (most recent call last):
File "/Users/kz/Documents/C/ansibleprj/venv/lib/python3.7/site-packages/ansible/executor/task_executor.py", line 139, in run
res = self._execute()
File "/Users/kz/Documents/C/ansibleprj/venv/lib/python3.7/site-packages/ansible/executor/task_executor.py", line 577, in _execute
result = self._handler.run(task_vars=variables)
File "/Users/kz/Documents/C/ansibleprj/venv/lib/python3.7/site-packages/ansible/plugins/action/telnet.py", line 69, in run
tn.read_until(login_prompt)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/telnetlib.py", line 302, in read_until
i = self.cookedq.find(match)
TypeError: argument should be integer or bytes-like object, not 'AnsibleUnicode'
fatal: [sw1]: FAILED! => {
"msg": "Unexpected failure during module execution.",
"stdout": ""
}
to retry, use: --limit @/Users/kz/Documents/C/ansibleprj/pys/pb_telnet.retryPLAY RECAP ********************************************************************************************************************************************
sw1 : ok=0 changed=0 unreachable=0 failed=1

Searched on Google and find it’s a known issue in current version:Ansible Cisco IOS – Telnet, what I did is copy the latest telnet.py from telnet action plugin type error #43609 to my ansible (Share my path for reference: /Users/kz/Documents/C/ansibleprj/venv/lib/python3.7/site-packages/ansible/plugins/action/telnet.py).

Run the playbook again:

ansible-playbook -i inventory_telnet.yml pb_telnet.yml

everything works as expected:

PLAY [show version] ***********************************************************************************************************************************
TASK [telnet sw] **************************************************************************************************************************************
changed: [sw1]
PLAY RECAP ********************************************************************************************************************************************
sw1 : ok=1 changed=1 unreachable=0 failed=0

Part 2

A minor Improvement

I still don’t want to save the password in cleartext as what I did in Part 1 (the password was defined in the playbook).

But I find it will only retrieve the password form playbook and won’t try the credential which was defined in inventory file after I went through telnet.py:

Telnet.py
Telnet.py

so, refer to other modules, I made some change: ansible-telnet so that that telnet.py can retrieve the info from inventory file. update the inventory with the telnet.py:

---

telnet:
  hosts:
    sw1:
      ansible_host: "192.168.1.100"
      connection: local
      ansible_ssh_user: test
      ansible_ssh_pass: test123
      ansible_ssh_timeout: 2
      ansible_ssh_port: 23

then playbook file:

---

- name: show version
  hosts: telnet
  gather_facts: false
  # connection: local
  tasks:
    - name: CM via telnet
      telnet:
        login_prompt: "Username: "
        prompts:
          - '[>|#]'
        command:
          - terminal length 0
          - show version

Verify the change by the same cmd:

ansible-playbook -i inventory_telnet.yml pb_telnet.yml
encrypt the password

password can be encrypted by “ansible-vault” command:

ansible-vault encrypt_string --vault-id test@prompt 'test123' --name 'ansible_ssh_pass'

I use the password: test123 to encrypt the string.

Update the hashed string to inventory file:

---

telnet:
hosts:
    sw1:
    ansible_host: "192.168.1.100"
    connection: local
    ansible_ssh_user: test
    ansible_ssh_pass: !vault |
        $ANSIBLE_VAULT;1.2;AES256;test
        63386332356237643731346539336262336231343432313963376438653933323737636535383365
        3562383633646261653739343536386566323462323063320a613638373439363032353137343330
        34306264613932323832373532636230323730626239393564326564303563356666343734633135
        6664373266663238660a363666336661353364393437356433616462346331313537623430393861
        3536
    ansible_ssh_timeout: 2
    ansible_ssh_port: 23

Verify it by the command:

ansible-playbook -i inventory_telnet.yml --vault-id test@prompt pb_telnet.yml
result with vault
result with vault

Part 3

Update1 (20180916)

Thanks for Stefan P’s comment about the command: “show version | include uptime”. Tried it and find that the strings after ‘|’ were cutted:

Pipe
Pipe

Read the code

  • line 80 of telnet.py:

    tn.expect(list(map(to_bytes, prompts)))
    
  • line 652 and 654 of telnet.py:

    return self._expect_with_poll(list, timeout)
    returnself._expect_with_select(list, timeout)
    
  • line 667 and line 731 of telnet.py:

    list[i] = re.compile(list[i])
    

prompts of playbook actually work as the regular expression match, this is why “|” in ansible official sample because ‘|’ works for either ‘>’ or ‘ #’. Since [] in Python RE works as single character match, remove “|” from playbook can work:

---

- name: show version
  hosts: telnet
  gather_facts: false
  # connection: local
  tasks:
    - name: CM via telnet
      telnet:
        login_prompt: "Username: "
        prompts:
          - '[>#]'
        command:
          - terminal length 0
          - show version | include uptime
          - show version | include Processor board ID
          - exit

Output:

works
works

Ansible-Access Network via Telnet


donation

Scan the QR code using WeChat

comments powered by Disqus