From 726d5218877821f7a051bf855302c0b7a8413335 Mon Sep 17 00:00:00 2001 From: Rick Sherman Date: Thu, 13 Apr 2017 17:18:07 -0500 Subject: [PATCH] (NETDEV-29) Enhance netdev NTP types This commit enhances the existing ntp_config and ntp_server methods and adds ntp_auth_key ntp_config - authenticate - trusted_key ntp_server - key - maxpoll - minpoll - source_interface - vrf This commit adds ntp_auth_key ntp_auth_key - key - algorithm - mode - password --- CHANGELOG.md | 28 +++++++ .../cmd_ref/ntp_auth_key.yaml | 10 +++ lib/cisco_node_utils/cmd_ref/ntp_config.yaml | 13 ++++ lib/cisco_node_utils/cmd_ref/ntp_server.yaml | 12 ++- lib/cisco_node_utils/ntp_auth_key.rb | 67 ++++++++++++++++ lib/cisco_node_utils/ntp_config.rb | 20 ++++- lib/cisco_node_utils/ntp_server.rb | 55 ++++++------- tests/test_ntp_auth_key.rb | 77 ++++++++++++++++++ tests/test_ntp_config.rb | 55 ++++++++++++- tests/test_ntp_server.rb | 78 ++++++++++++++++--- 10 files changed, 372 insertions(+), 43 deletions(-) create mode 100644 lib/cisco_node_utils/cmd_ref/ntp_auth_key.yaml create mode 100644 lib/cisco_node_utils/ntp_auth_key.rb create mode 100644 tests/test_ntp_auth_key.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 50d5d961..64d7ea02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,10 +17,38 @@ Changelog * Extend interface_channel_group with attributes: * `channel_group_mode` +* Extend ntp_config with attributes: + * `authenticate` + * `trusted_key` + +* Extend ntp_server with attributes: + * `key` + * `maxpoll` + * `minpoll` + * `source_interface` + * `vrf` + +* Added ntp_auth_key with attributes: + * `algorithm` + * `key` + * `mode` + * `password` + * Extend upgrade with attributes: * `package` ### Changed +* ntp_server initialize now uses options hash + * Prior to this release ntp_server accepted positional arguments for id and + prefer. New behavior is to pass attributes as a hash. + + Example: + ``` + options = { 'name' => id, 'key' => '999', 'prefer' => 'true', + 'minpoll' => '5', 'maxpoll' => '8', 'vrf' => 'red' } + Cisco::NtpServer.new(options, true) + ``` + * Modified upgrade to support additional URI * Modified upgrade attribute to drop version check diff --git a/lib/cisco_node_utils/cmd_ref/ntp_auth_key.yaml b/lib/cisco_node_utils/cmd_ref/ntp_auth_key.yaml new file mode 100644 index 00000000..c1b35369 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/ntp_auth_key.yaml @@ -0,0 +1,10 @@ +# ntp_auth_key +--- +key: + multiple: true + default_value: [] + nexus: + get_command: "show running-config all | include 'ntp authentication-key'" + # Returns , , , + get_value: '/^ntp authentication-key (\d+) (\w+) (\S+) (\d)/' + set_value: ' ntp authentication-key ' diff --git a/lib/cisco_node_utils/cmd_ref/ntp_config.yaml b/lib/cisco_node_utils/cmd_ref/ntp_config.yaml index 49c91b6a..1f850c05 100644 --- a/lib/cisco_node_utils/cmd_ref/ntp_config.yaml +++ b/lib/cisco_node_utils/cmd_ref/ntp_config.yaml @@ -3,6 +3,13 @@ _template: get_command: "show running-config ntp" +authenticate: + kind: boolean + default_value: false + nexus: + get_value: '/^ntp authenticate$/' + set_value: " ntp authenticate" + source_interface: default_value: ~ nexus: @@ -12,3 +19,9 @@ source_interface: context: ["ntp"] get_value: '/source\s+(.*)$/' set_value: ' source ' + +trusted_key: + multiple: + nexus: + get_value: '/^ntp trusted-key\s+(\d+)$/' + set_value: " ntp trusted-key " diff --git a/lib/cisco_node_utils/cmd_ref/ntp_server.yaml b/lib/cisco_node_utils/cmd_ref/ntp_server.yaml index e485f347..00ccb319 100644 --- a/lib/cisco_node_utils/cmd_ref/ntp_server.yaml +++ b/lib/cisco_node_utils/cmd_ref/ntp_server.yaml @@ -5,6 +5,13 @@ _template: context: ["ntp"] get_command: "show running-config ntp" +key: + multiple: true + default_value: [] + nexus: + get_command: "show running-config all | include 'ntp server(.*)(key)'" + get_value: '/ntp server (\S+)/' + prefer: multiple: true default_value: [] @@ -19,8 +26,9 @@ server: default_value: [] nexus: get_command: "show running-config all | include 'ntp server'" - get_value: '/ntp server (\S+)/' - set_value: ' ntp server ' + # Returns , , , , , + get_value: '/^(?:ntp server )([^\s]+) (prefer )?(?:(?:use-vrf )([^\s]+))?(?:(?: key )(\d+))?(?:(?: minpoll )(\d+))?(?:(?: maxpoll )(\d+))?/' + set_value: ' ntp server ' ios_xr: get_value: '/server (?:ipv6 )?(\S+)/' set_value: ' server ' diff --git a/lib/cisco_node_utils/ntp_auth_key.rb b/lib/cisco_node_utils/ntp_auth_key.rb new file mode 100644 index 00000000..9441b14e --- /dev/null +++ b/lib/cisco_node_utils/ntp_auth_key.rb @@ -0,0 +1,67 @@ +# NTP Authentication key provider class +# +# Rick Sherman et al., April 2017 +# +# Copyright (c) 2014-2017 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'node' + +module Cisco + # NtpAuthKey - node utility class for NTP authentication-key management + class NtpAuthKey < NodeUtil + attr_reader :algorithm, :mode, :password + + def initialize(opts, instantiate=true) + @algorithm = opts['algorithm'].nil? ? 'md5' : opts['algorithm'] + @key = opts['name'] + @mode = opts['mode'].nil? ? '7' : opts['mode'] + @password = opts['password'] + + create if instantiate + end + + def self.ntpkeys + keys = %w(name algorithm password mode) + hash = {} + ntp_auth_key_list = config_get('ntp_auth_key', 'key') + return hash if ntp_auth_key_list.empty? + + ntp_auth_key_list.each do |id| + hash[id[0]] = NtpAuthKey.new(Hash[keys.zip(id)], false) + end + + hash + end + + def ==(other) + name == other.name + end + + def create + config_set('ntp_auth_key', 'key', state: '', key: @key, + algorithm: @algorithm, password: @password, mode: @mode) + end + + def destroy + # There appears to be a bug in NXOS that requires the password be passed + config_set('ntp_auth_key', 'key', state: 'no', key: @key, + algorithm: @algorithm, password: @password, mode: @mode) + end + + def name + @key + end + end # class +end # module diff --git a/lib/cisco_node_utils/ntp_config.rb b/lib/cisco_node_utils/ntp_config.rb index 94423895..bf80e5fb 100644 --- a/lib/cisco_node_utils/ntp_config.rb +++ b/lib/cisco_node_utils/ntp_config.rb @@ -2,7 +2,7 @@ # # Jonathan Tripathy et al., September 2015 # -# Copyright (c) 2014-2016 Cisco and/or its affiliates. +# Copyright (c) 2014-2017 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -41,6 +41,24 @@ def ==(other) name == other.name end + def authenticate + config_get('ntp_config', 'authenticate') + end + + def authenticate=(enable) + state = (enable ? '' : 'no') + config_set('ntp_config', 'authenticate', state: state) + end + + def trusted_key + config_get('ntp_config', 'trusted_key') + end + + def trusted_key_set(state, key) + state = (state ? '' : 'no') + config_set('ntp_config', 'trusted_key', state: state, key: key) + end + def source_interface source_interface = config_get('ntp_config', 'source_interface') source_interface = source_interface.downcase \ diff --git a/lib/cisco_node_utils/ntp_server.rb b/lib/cisco_node_utils/ntp_server.rb index 4eaecc45..8e194f65 100644 --- a/lib/cisco_node_utils/ntp_server.rb +++ b/lib/cisco_node_utils/ntp_server.rb @@ -2,7 +2,7 @@ # # Jonathan Tripathy et al., September 2015 # -# Copyright (c) 2014-2016 Cisco and/or its affiliates. +# Copyright (c) 2014-2017 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,43 +18,43 @@ require_relative 'node' require_relative 'interface' +require 'resolv' module Cisco # NtpServer - node utility class for NTP Server configuration management class NtpServer < NodeUtil - def initialize(ntpserver_id, prefer, instantiate=true) - @ntpserver_id = ntpserver_id.to_s - @ntpserver_prefer = prefer + attr_reader :key, :maxpoll, :minpoll, :prefer, :vrf - unless @ntpserver_id =~ /^[a-zA-Z0-9\.\:]*$/ - fail ArgumentError, - 'Invalid value (IPv4/IPv6 address contains invalid characters)' - end + def initialize(opts, instantiate=true) + @ntpserver_id = opts['name'] + @key = opts['key'] + @minpoll = opts['minpoll'] + @maxpoll = opts['maxpoll'] + @prefer = opts['prefer'].nil? ? false : true + @vrf = opts['vrf'].nil? ? 'default' : opts['vrf'] - begin - IPAddr.new(@ntpserver_id) - rescue - raise ArgumentError, - 'Invalid value (Name is not a valid single IPv4/IPv6 address)' - end + hostname_regex = /^(?=.{1,255}$)[0-9A-Za-z] + (?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])? + (?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/x - unless @ntpserver_prefer == true || - @ntpserver_prefer == false || - @ntpserver_prefer.nil? - fail ArgumentError, 'Invalid value(prefer must be true or false)' + unless @ntpserver_id =~ Resolv::AddressRegex || + @ntpserver_id =~ hostname_regex + fail ArgumentError, + "Invalid value '#{@ntpserver_id}' \ + (Must be valid IPv4/IPv6 address or hostname)" end + create if instantiate end def self.ntpservers + keys = %w(name prefer vrf key minpoll maxpoll) hash = {} ntpservers_list = config_get('ntp_server', 'server') return hash if ntpservers_list.empty? - preferred_servers = config_get('ntp_server', 'prefer') - ntpservers_list.each do |id| - hash[id] = NtpServer.new(id, preferred_servers.include?(id), false) + hash[id[0]] = NtpServer.new(Hash[keys.zip(id)], false) end hash @@ -66,20 +66,21 @@ def ==(other) def create config_set('ntp_server', 'server', state: '', ip: @ntpserver_id, - prefer: @ntpserver_prefer ? 'prefer' : '') + prefer: (['true', true].include? @prefer) ? 'prefer' : '', + vrf: @vrf ? "use-vrf #{@vrf}" : '', + key: @key ? "key #{@key}" : '', + minpoll: @minpoll ? "minpoll #{@minpoll}" : '', + maxpoll: @maxpoll ? "maxpoll #{@maxpoll}" : '') end def destroy config_set('ntp_server', 'server', - state: 'no', ip: @ntpserver_id, prefer: '') + state: 'no', ip: @ntpserver_id, prefer: '', vrf: '', + key: '', minpoll: '', maxpoll: '') end def name @ntpserver_id end - - def prefer - @ntpserver_prefer - end end # class end # module diff --git a/tests/test_ntp_auth_key.rb b/tests/test_ntp_auth_key.rb new file mode 100644 index 00000000..c52945a8 --- /dev/null +++ b/tests/test_ntp_auth_key.rb @@ -0,0 +1,77 @@ +# +# Minitest for NtpAuthKey class +# +# Copyright (c) 2014-2017 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'ciscotest' +require_relative '../lib/cisco_node_utils/ntp_auth_key' + +# TestNtpAuthKey - Minitest for NtpAuthKey node utility. +class TestNtpAuthKey < CiscoTestCase + def setup + # setup runs at the beginning of each test + super + no_ntpkey + end + + def teardown + # teardown runs at the end of each test + no_ntpkey + super + end + + def no_ntpkey + # Turn the feature off for a clean test. + config('no ntp authentication-key 111 md5 test 7', + 'no ntp authentication-key 999 md5 test 7') + end + + # TESTS + + def test_create_defaults + id = '111' + options = { 'name' => id, 'password' => 'test' } + refute_includes(Cisco::NtpAuthKey.ntpkeys, id) + + key = Cisco::NtpAuthKey.new(options, true) + assert_includes(Cisco::NtpAuthKey.ntpkeys, id) + assert_equal(key, Cisco::NtpAuthKey.ntpkeys[id]) + + assert_equal(id, Cisco::NtpAuthKey.ntpkeys[id].name) + assert_equal('md5', Cisco::NtpAuthKey.ntpkeys[id].algorithm) + assert_equal('7', Cisco::NtpAuthKey.ntpkeys[id].mode) + + key.destroy + refute_includes(Cisco::NtpAuthKey.ntpkeys, id) + end + + def test_create_options + id = '999' + options = { 'name' => id, 'password' => 'test', 'algorithm' => 'md5', + 'mode' => '7' } + refute_includes(Cisco::NtpAuthKey.ntpkeys, id) + + key = Cisco::NtpAuthKey.new(options, true) + assert_includes(Cisco::NtpAuthKey.ntpkeys, id) + assert_equal(key, Cisco::NtpAuthKey.ntpkeys[id]) + + assert_equal(id, Cisco::NtpAuthKey.ntpkeys[id].name) + assert_equal('md5', Cisco::NtpAuthKey.ntpkeys[id].algorithm) + assert_equal('7', Cisco::NtpAuthKey.ntpkeys[id].mode) + + key.destroy + refute_includes(Cisco::NtpAuthKey.ntpkeys, id) + end +end diff --git a/tests/test_ntp_config.rb b/tests/test_ntp_config.rb index 7b712c3e..298909aa 100644 --- a/tests/test_ntp_config.rb +++ b/tests/test_ntp_config.rb @@ -1,7 +1,7 @@ # # Minitest for NtpConfig class # -# Copyright (c) 2015-2016 Cisco and/or its affiliates. +# Copyright (c) 2015-2017 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -26,28 +26,75 @@ def setup no_ntpconfig end + def teardown + # teardown runs at the end of each test + no_ntpconfig + super + end + def no_ntpconfig # Turn the feature off for a clean test. if platform == :ios_xr - config("no ntp source #{interfaces[0]}") + config("no ntp source #{interfaces[1]}") else - config("no ntp source-interface #{interfaces[0]}") + config("no ntp source-interface #{interfaces[1]}", + 'no ntp authentication-key 111 md5 test 7', + 'no ntp authentication-key 999 md5 test 7', + 'no ntp trusted-key 111', + 'no ntp trusted-key 999', + 'no ntp authenticate') end end # TESTS - def test_create_destroy + def test_source_interface id = 'default' ntp = Cisco::NtpConfig.new(id) assert_includes(Cisco::NtpConfig.ntpconfigs, id) assert_equal(Cisco::NtpConfig.ntpconfigs[id], ntp) + assert_nil(Cisco::NtpConfig.ntpconfigs[id].source_interface) + assert_nil(ntp.source_interface) + ntp.source_interface = interfaces[1] assert_equal(Cisco::NtpConfig.ntpconfigs[id].source_interface, interfaces[1].downcase) assert_equal(Cisco::NtpConfig.ntpconfigs[id].source_interface, ntp.source_interface) + + ntp.source_interface = nil + assert_nil(Cisco::NtpConfig.ntpconfigs[id].source_interface) + assert_nil(ntp.source_interface) + end + + def test_authenticate + id = 'default' + + ntp = Cisco::NtpConfig.new(id) + assert_includes(Cisco::NtpConfig.ntpconfigs, id) + assert_equal(Cisco::NtpConfig.ntpconfigs[id], ntp) + + assert_equal(false, Cisco::NtpConfig.ntpconfigs[id].authenticate) + + ntp.authenticate = true + assert_equal(true, Cisco::NtpConfig.ntpconfigs[id].authenticate) + end + + def test_trusted_key + id = 'default' + + ntp = Cisco::NtpConfig.new(id) + assert_includes(Cisco::NtpConfig.ntpconfigs, id) + assert_equal(Cisco::NtpConfig.ntpconfigs[id], ntp) + + assert_nil(Cisco::NtpConfig.ntpconfigs[id].trusted_key) + + config('ntp authentication-key 111 md5 test 7', + 'ntp authentication-key 999 md5 test 7') + ntp.trusted_key_set(true, 111) + ntp.trusted_key_set(true, 999) + assert_equal(%w(111 999), Cisco::NtpConfig.ntpconfigs[id].trusted_key) end end diff --git a/tests/test_ntp_server.rb b/tests/test_ntp_server.rb index 47ef880c..8cdd82f1 100644 --- a/tests/test_ntp_server.rb +++ b/tests/test_ntp_server.rb @@ -1,7 +1,7 @@ # # Minitest for NtpServer class # -# Copyright (c) 2014-2016 Cisco and/or its affiliates. +# Copyright (c) 2014-2017 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,28 +23,37 @@ class TestNtpServer < CiscoTestCase def setup # setup runs at the beginning of each test super - no_ntpserver_uk + no_ntpserver end def teardown # teardown runs at the end of each test - no_ntpserver_uk + no_ntpserver super end - def no_ntpserver_uk + def no_ntpserver # Turn the feature off for a clean test. config('no ntp server 130.88.203.12', - 'no ntp server 2003::5') + 'no ntp server 2003::5', + 'no ntp server 0.us.pool.ntp.org', + 'no ntp authentication-key 999 md5 test 7', + 'no vrf context red') end # TESTS + def test_create_name_invalid + assert_raises(ArgumentError) do + Cisco::NtpServer.new({ 'name' => '1_com' }, true) + end + end + def test_ipv4 id = '130.88.203.12' refute_includes(Cisco::NtpServer.ntpservers, id) - ntp = Cisco::NtpServer.new(id, false) + ntp = Cisco::NtpServer.new({ 'name' => id }, true) assert_includes(Cisco::NtpServer.ntpservers, id) assert_equal(ntp, Cisco::NtpServer.ntpservers[id]) @@ -56,7 +65,7 @@ def test_ipv6 id = '2003::5' refute_includes(Cisco::NtpServer.ntpservers, id) - ntp = Cisco::NtpServer.new(id, false) + ntp = Cisco::NtpServer.new({ 'name' => id }, true) assert_includes(Cisco::NtpServer.ntpservers, id) assert_equal(ntp, Cisco::NtpServer.ntpservers[id]) @@ -70,8 +79,8 @@ def test_multiple refute_includes(Cisco::NtpServer.ntpservers, id1) refute_includes(Cisco::NtpServer.ntpservers, id2) - ntp1 = Cisco::NtpServer.new(id1, false) - ntp2 = Cisco::NtpServer.new(id2, true) + ntp1 = Cisco::NtpServer.new({ 'name' => id1 }, true) + ntp2 = Cisco::NtpServer.new({ 'name' => id2 }, true) refute_equal(ntp1, ntp2) assert_includes(Cisco::NtpServer.ntpservers, id1) assert_includes(Cisco::NtpServer.ntpservers, id2) @@ -83,4 +92,55 @@ def test_multiple refute_includes(Cisco::NtpServer.ntpservers, id1) refute_includes(Cisco::NtpServer.ntpservers, id2) end + + # This test requires DNS resolution be avaabile - leaving for reference + # def test_domain_name + # id = '0.us.pool.ntp.org' + # refute_includes(Cisco::NtpServer.ntpservers, id) + # + # ntp = Cisco::NtpServer.new({ 'name' => id }, true) + # assert_includes(Cisco::NtpServer.ntpservers, id) + # assert_equal(ntp, Cisco::NtpServer.ntpservers[id]) + # + # ntp.destroy + # refute_includes(Cisco::NtpServer.ntpservers, id) + # end + + def test_defaults + id = '130.88.203.12' + refute_includes(Cisco::NtpServer.ntpservers, id) + + ntp = Cisco::NtpServer.new({ 'name' => id }, true) + assert_includes(Cisco::NtpServer.ntpservers, id) + assert_equal('default', Cisco::NtpServer.ntpservers[id].vrf) + assert_nil(Cisco::NtpServer.ntpservers[id].key) + assert_nil(Cisco::NtpServer.ntpservers[id].maxpoll) + assert_nil(Cisco::NtpServer.ntpservers[id].minpoll) + refute(Cisco::NtpServer.ntpservers[id].prefer) + + ntp.destroy + refute_includes(Cisco::NtpServer.ntpservers, id) + end + + def test_create_options + id = '130.88.203.12' + refute_includes(Cisco::NtpServer.ntpservers, id) + + options = { 'name' => id, 'key' => '999', 'prefer' => 'true', + 'minpoll' => '5', 'maxpoll' => '8', 'vrf' => 'red' } + + config('vrf context red') + config('ntp authentication-key 999 md5 test 7') + + ntp = Cisco::NtpServer.new(options, true) + assert_includes(Cisco::NtpServer.ntpservers, id) + assert_equal('red', Cisco::NtpServer.ntpservers[id].vrf) + assert_equal('999', Cisco::NtpServer.ntpservers[id].key) + assert_equal('5', Cisco::NtpServer.ntpservers[id].minpoll) + assert_equal('8', Cisco::NtpServer.ntpservers[id].maxpoll) + assert(Cisco::NtpServer.ntpservers[id].prefer) + + ntp.destroy + refute_includes(Cisco::NtpServer.ntpservers, id) + end end