create cisco type 5 filters (#39901)
This commit is contained in:
parent
c29b03e77b
commit
dd02a4e943
2 changed files with 143 additions and 3 deletions
|
@ -23,13 +23,17 @@ __metaclass__ = type
|
|||
import re
|
||||
import os
|
||||
import traceback
|
||||
import string
|
||||
|
||||
from collections import Mapping
|
||||
from xml.etree.ElementTree import fromstring
|
||||
|
||||
from ansible.module_utils.network.common.utils import Template
|
||||
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:
|
||||
import yaml
|
||||
|
@ -50,6 +54,12 @@ except ImportError:
|
|||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
try:
|
||||
from passlib.hash import md5_crypt
|
||||
HAS_PASSLIB = True
|
||||
except ImportError:
|
||||
HAS_PASSLIB = False
|
||||
|
||||
|
||||
def re_matchall(regex, value):
|
||||
objects = list()
|
||||
|
@ -345,13 +355,56 @@ def parse_xml(output, tmpl):
|
|||
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):
|
||||
"""Filters for working with output from network devices"""
|
||||
|
||||
filter_map = {
|
||||
'parse_cli': parse_cli,
|
||||
'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):
|
||||
|
|
|
@ -20,8 +20,11 @@ __metaclass__ = type
|
|||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
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')
|
||||
|
||||
|
@ -78,3 +81,87 @@ class TestNetworkParseFilter(unittest.TestCase):
|
|||
with self.assertRaises(Exception) as e:
|
||||
parse_xml(output, spec_file_path)
|
||||
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)
|
||||
|
|
Loading…
Reference in a new issue