create cisco type 5 filters ()

This commit is contained in:
Ken Celenza 2018-05-22 08:35:54 -04:00 committed by John R Barker
parent c29b03e77b
commit dd02a4e943
2 changed files with 143 additions and 3 deletions
lib/ansible/plugins/filter
test/units/plugins/filter

View file

@ -23,13 +23,17 @@ __metaclass__ = type
import re import re
import os import os
import traceback import traceback
import string
from collections import Mapping from collections import Mapping
from xml.etree.ElementTree import fromstring from xml.etree.ElementTree import fromstring
from ansible.module_utils.network.common.utils import Template from ansible.module_utils.network.common.utils import Template
from ansible.module_utils.six import iteritems, string_types from ansible.module_utils.six import iteritems, string_types
from ansible.errors import AnsibleError from ansible.errors import AnsibleError, AnsibleFilterError
from ansible.utils.encrypt import random_password
from ansible.plugins.lookup import password as ansible_password
try: try:
import yaml import yaml
@ -50,6 +54,12 @@ except ImportError:
from ansible.utils.display import Display from ansible.utils.display import Display
display = Display() display = Display()
try:
from passlib.hash import md5_crypt
HAS_PASSLIB = True
except ImportError:
HAS_PASSLIB = False
def re_matchall(regex, value): def re_matchall(regex, value):
objects = list() objects = list()
@ -345,13 +355,56 @@ def parse_xml(output, tmpl):
return obj return obj
def type5_pw(password, salt=None):
if not HAS_PASSLIB:
raise AnsibleFilterError('type5_pw filter requires PassLib library to be installed')
if not isinstance(password, string_types):
raise AnsibleFilterError("type5_pw password input should be a string, but was given a input of %s" % (type(password).__name__))
salt_chars = ansible_password._gen_candidate_chars(['ascii_letters', 'digits', './'])
if salt is not None and not isinstance(salt, string_types):
raise AnsibleFilterError("type5_pw salt input should be a string, but was given a input of %s" % (type(salt).__name__))
elif not salt:
salt = random_password(length=4, chars=salt_chars)
elif not set(salt) <= set(salt_chars):
raise AnsibleFilterError("type5_pw salt used inproper characters, must be one of %s" % (salt_chars))
encrypted_password = md5_crypt.encrypt(password, salt=salt)
return encrypted_password
def hash_salt(password):
split_password = password.split("$")
if len(split_password) != 4:
raise AnsibleFilterError('Could not parse salt out password correctly from {0}'.format(password))
else:
return split_password[2]
def comp_type5(unencrypted_password, encrypted_password, return_orginal=False):
salt = hash_salt(encrypted_password)
if type5_pw(unencrypted_password, salt) == encrypted_password:
if return_orginal is True:
return encrypted_password
else:
return True
return False
class FilterModule(object): class FilterModule(object):
"""Filters for working with output from network devices""" """Filters for working with output from network devices"""
filter_map = { filter_map = {
'parse_cli': parse_cli, 'parse_cli': parse_cli,
'parse_cli_textfsm': parse_cli_textfsm, 'parse_cli_textfsm': parse_cli_textfsm,
'parse_xml': parse_xml 'parse_xml': parse_xml,
'type5_pw': type5_pw,
'hash_salt': hash_salt,
'comp_type5': comp_type5
} }
def filters(self): def filters(self):

View file

@ -20,8 +20,11 @@ __metaclass__ = type
import os import os
import sys import sys
import pytest
from ansible.compat.tests import unittest from ansible.compat.tests import unittest
from ansible.plugins.filter.network import parse_xml from ansible.plugins.filter.network import parse_xml, type5_pw, hash_salt, comp_type5
from ansible.errors import AnsibleFilterError
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'network') fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'network')
@ -78,3 +81,87 @@ class TestNetworkParseFilter(unittest.TestCase):
with self.assertRaises(Exception) as e: with self.assertRaises(Exception) as e:
parse_xml(output, spec_file_path) parse_xml(output, spec_file_path)
self.assertEqual("parse_xml works on string input, but given input of : %s" % type(output), str(e.exception)) self.assertEqual("parse_xml works on string input, but given input of : %s" % type(output), str(e.exception))
class TestNetworkType5(unittest.TestCase):
def test_defined_salt_success(self):
password = 'cisco'
salt = 'nTc1'
expected = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.'
parsed = type5_pw(password, salt)
self.assertEqual(parsed, expected)
def test_undefined_salt_success(self):
password = 'cisco'
parsed = type5_pw(password)
self.assertEqual(len(parsed), 30)
def test_wrong_data_type(self):
with self.assertRaises(Exception) as e:
type5_pw([])
self.assertEqual("type5_pw password input should be a string, but was given a input of list", str(e.exception))
with self.assertRaises(Exception) as e:
type5_pw({})
self.assertEqual("type5_pw password input should be a string, but was given a input of dict", str(e.exception))
with self.assertRaises(Exception) as e:
type5_pw('pass', [])
self.assertEqual("type5_pw salt input should be a string, but was given a input of list", str(e.exception))
with self.assertRaises(Exception) as e:
type5_pw('pass', {})
self.assertEqual("type5_pw salt input should be a string, but was given a input of dict", str(e.exception))
def test_bad_salt_char(self):
with self.assertRaises(Exception) as e:
type5_pw('password', '*()')
self.assertEqual("type5_pw salt used inproper characters, must be one of "
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./", str(e.exception))
with self.assertRaises(Exception) as e:
type5_pw('password', 'asd$')
self.assertEqual("type5_pw salt used inproper characters, must be one of "
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./", str(e.exception))
class TestHashSalt(unittest.TestCase):
def test_retrieve_salt(self):
password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.'
parsed = hash_salt(password)
self.assertEqual(parsed, 'nTc1')
password = '$2y$14$wHhBmAgOMZEld9iJtV.'
parsed = hash_salt(password)
self.assertEqual(parsed, '14')
def test_unparseable_salt(self):
password = '$nTc1$Z28sUTcWfXlvVe2x.3XAa.'
with self.assertRaises(Exception) as e:
parsed = hash_salt(password)
self.assertEqual("Could not parse salt out password correctly from $nTc1$Z28sUTcWfXlvVe2x.3XAa.", str(e.exception))
class TestCompareType5(unittest.TestCase):
def test_compare_type5_boolean(self):
unencrypted_password = 'cisco'
encrypted_password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.'
parsed = comp_type5(unencrypted_password, encrypted_password)
self.assertEqual(parsed, True)
def test_compare_type5_string(self):
unencrypted_password = 'cisco'
encrypted_password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.'
parsed = comp_type5(unencrypted_password, encrypted_password, True)
self.assertEqual(parsed, '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.')
def test_compate_type5_fail(self):
unencrypted_password = 'invalid_password'
encrypted_password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.'
parsed = comp_type5(unencrypted_password, encrypted_password)
self.assertEqual(parsed, False)