diff --git a/xCAT-test/unit/ddns_omapi_policy.t b/xCAT-test/unit/ddns_omapi_policy.t index a0ccc8aed..9ee18ec80 100644 --- a/xCAT-test/unit/ddns_omapi_policy.t +++ b/xCAT-test/unit/ddns_omapi_policy.t @@ -7,18 +7,20 @@ use lib "$FindBin::Bin/../../xCAT-server/lib"; use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; use lib "$FindBin::Bin/../../perl-xCAT"; +use File::Temp qw(tempfile); use Test::More; $ENV{XCATCFG} ||= 'SQLite:/tmp'; $ENV{XCATROOT} ||= "$FindBin::Bin/../../xCAT-server"; -my $source_ddns_plugin = +my $ddns_plugin_path = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/ddns.pm"; -if ( -f $source_ddns_plugin ) { - require $source_ddns_plugin; +if ( -f $ddns_plugin_path ) { + require $ddns_plugin_path; } else { require xCAT_plugin::ddns; + $ddns_plugin_path = $INC{'xCAT_plugin/ddns.pm'}; } sub omapi_settings { @@ -41,6 +43,15 @@ local %XCATSITEVALS = ( dhcpomshellpath => '/opt/site/bin/omshell', ); +my @net_dns_versions = ( + [ '1.09', 0 ], + [ '1.35', 0 ], + [ '1.36', 1 ], + [ '1.40', 1 ], + [ '1.100', 1 ], + [ '2.0', 1 ], +); + my $defaults = omapi_settings(); is( xCAT_plugin::ddns::ddns_key_contents( @@ -79,4 +90,193 @@ is( 'custom DDNS key name and algorithm are rendered' ); +subtest 'all Net::DNS thresholds share the dotted version policy' => sub { + open( my $source_fh, '<', $ddns_plugin_path ) + or die "Unable to read $ddns_plugin_path: $!"; + local $/; + my $source = <$source_fh>; + close($source_fh) + or die "Unable to close $ddns_plugin_path: $!"; + + my @raw_comparisons = + ( $source =~ /^(?!\s*#)[^\n]*(?:<|>=)\s*1\.36\b/gm ); + is( scalar(@raw_comparisons), 0, + 'no Net::DNS threshold uses Perl numeric comparison' ); + + my @policy_calls = ( $source =~ /net_dns_uses_keyfile\(\)/g ); + is( scalar(@policy_calls), 4, + 'all four Net::DNS threshold sites use the shared policy' ); +}; + +subtest 'Net::DNS threshold controls DDNS policy and signing' => sub { + my $implicit_sha256 = { + algorithm => 'hmac-sha256', + algorithm_explicit => 0, + }; + + foreach my $case (@net_dns_versions) { + my ( $version, $uses_keyfile ) = @{$case}; + my $expected_algorithm = $uses_keyfile ? 'hmac-sha256' : 'hmac-md5'; + is( + with_net_dns_version( + $version, + sub { + xCAT_plugin::ddns::ddns_tsig_algorithm( + { omapi_settings => $implicit_sha256 } + ); + } + ), + $expected_algorithm, + "Net::DNS $version selects the expected implicit algorithm" + ); + + my $update = Local::DDNS::Update->new(); + with_net_dns_version( + $version, + sub { + xCAT_plugin::ddns::ddns_sign_update( + { + omapi_settings => $defaults, + privkey => 'legacy-secret', + }, + $update + ); + } + ); + my $expected_call = $uses_keyfile + ? [ '/etc/xcat/ddns.key' ] + : [ 'xcat_key', 'legacy-secret' ]; + is_deeply( + $update->{sign_tsig_calls}, + [$expected_call], + "Net::DNS $version signs through the expected interface" + ); + + my $tracker = tie my %key_context, 'Local::DDNS::TrackingHash'; + with_net_dns_version( + $version, + sub { + xCAT_plugin::ddns::ensure_ddns_key_file(\%key_context); + } + ); + is_deeply( + $tracker->{fetches}, + $uses_keyfile ? ['privkey'] : [], + "Net::DNS $version applies the expected keyfile write gate" + ); + } +}; + +subtest 'Net::DNS threshold controls named key reconciliation' => sub { + foreach my $case (@net_dns_versions) { + my ( $version, $uses_keyfile ) = @{$case}; + my ( $named_contents, $restartneeded ) = + reconcile_named_key($version); + my $expected_algorithm = $uses_keyfile ? 'hmac-sha256' : 'hmac-md5'; + + like( + $named_contents, + qr/^\s*algorithm\s+\Q$expected_algorithm\E\s*;/m, + "Net::DNS $version keeps the expected named key algorithm" + ); + is( + $restartneeded ? 1 : 0, + $uses_keyfile ? 0 : 1, + "Net::DNS $version records the expected named restart state" + ); + } +}; + done_testing(); + +sub with_net_dns_version { + my ( $version, $code ) = @_; + + local $Net::DNS::VERSION = $version; + return $code->(); +} + +sub reconcile_named_key { + my ($version) = @_; + + my ( $named_fh, $named_path ) = tempfile(UNLINK => 1); + print {$named_fh} + "options {\n};\n" + . "key \"xcat_key\" {\n" + . "\talgorithm hmac-sha256;\n" + . "\tsecret \"legacy-secret\";\n" + . "};\n"; + close($named_fh) or die "Unable to close $named_path: $!"; + + my $ctx = { + omapi_settings => omapi_settings(), + privkey => 'legacy-secret', + zonesdir => '/tmp', + dbdir => '/tmp', + zonestotouch => {}, + adzones => {}, + dnsupdaters => [], + adservers => [], + restartneeded => 0, + }; + + no warnings qw(redefine once); + local *xCAT_plugin::ddns::get_conf = sub { return $named_path; }; + local *xCAT_plugin::ddns::ensure_ddns_key_file = sub { return; }; + local *xCAT::TableUtils::get_site_attribute = sub { return; }; + local *xCAT::Utils::runcmd = sub { return (); }; + local *xCAT::Utils::isAIX = sub { return 0; }; + local *xCAT::Utils::isLinux = sub { return 1; }; + local *xCAT::Table::new = sub { + return bless {}, 'Local::DDNS::PasswdTable'; + }; + + with_net_dns_version( + $version, + sub { xCAT_plugin::ddns::update_namedconf( $ctx, 0 ); } + ); + + open( my $result_fh, '<', $named_path ) + or die "Unable to read $named_path: $!"; + local $/; + my $contents = <$result_fh>; + close($result_fh) or die "Unable to close $named_path: $!"; + + return ( $contents, $ctx->{restartneeded} ); +} + +{ + package Local::DDNS::Update; + + sub new { + return bless { sign_tsig_calls => [] }, shift; + } + + sub sign_tsig { + my ( $self, @args ) = @_; + push @{ $self->{sign_tsig_calls} }, \@args; + return; + } +} + +{ + package Local::DDNS::PasswdTable; + + sub setAttribs { + return 1; + } +} + +{ + package Local::DDNS::TrackingHash; + + sub TIEHASH { + return bless { fetches => [] }, shift; + } + + sub FETCH { + my ( $self, $key ) = @_; + push @{ $self->{fetches} }, $key; + return; + } +}