From ab8613995918620be093ca7360a9cab125760863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Thu, 7 May 2026 00:56:05 -0300 Subject: [PATCH] Support configurable ISC OMAPI TSIG policy Add a shared OMAPI policy helper for ISC DHCP and DDNS so administrators can select the key name, signing algorithm, and omshell path from the site table while preserving the existing xcat_key hmac-md5 default. Keep local ISC updates from hanging indefinitely when omshell does not exit, and use a static host-declaration fallback for local Ubuntu ISC releases where omshell is unstable for xCAT host updates. Co-authored-by: gskouson <1507929+gskouson@users.noreply.github.com> --- .../domain_name_resolution.rst | 22 +- .../admin-guides/references/man5/site.5.rst | 15 + .../references/man8/makedhcp.8.rst | 4 + perl-xCAT/xCAT/DHCP/OmapiPolicy.pm | 132 +++++++ perl-xCAT/xCAT/Schema.pm | 12 + xCAT-server/lib/xcat/plugins/ddns.pm | 90 +++-- xCAT-server/lib/xcat/plugins/dhcp.pm | 360 ++++++++++++++---- xCAT-server/share/xcat/tools/dhcpop | 59 ++- xCAT-test/unit/ddns_omapi_policy.t | 64 ++++ xCAT-test/unit/dhcp_omapi_policy.t | 95 +++++ 10 files changed, 738 insertions(+), 115 deletions(-) create mode 100644 perl-xCAT/xCAT/DHCP/OmapiPolicy.pm create mode 100644 xCAT-test/unit/ddns_omapi_policy.t create mode 100644 xCAT-test/unit/dhcp_omapi_policy.t diff --git a/docs/source/advanced/domain_name_resolution/domain_name_resolution.rst b/docs/source/advanced/domain_name_resolution/domain_name_resolution.rst index 063be13f2..8631d518f 100644 --- a/docs/source/advanced/domain_name_resolution/domain_name_resolution.rst +++ b/docs/source/advanced/domain_name_resolution/domain_name_resolution.rst @@ -90,6 +90,27 @@ For example: :: Edit **/etc/resolv.conf** to contain the cluster domain value you set in the site table's **domain** attribute above, and to point to the same DNS server you will be using for your nodes (if you are using DNS). +Legacy ISC DHCP and BIND TSIG Key Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +xCAT uses **xcat_key** with **hmac-md5** by default for legacy ISC DHCP OMAPI and BIND DDNS updates. Existing installations should keep that default unless a site policy or external DNS provider requires a different key. + +To use another supported algorithm, set **dhcpomapialgorithm** in the site table and update the matching **passwd** table secret. Supported values are **hmac-md5**, **hmac-sha1**, **hmac-sha224**, **hmac-sha256**, **hmac-sha384**, and **hmac-sha512**. For example: :: + + chdef -t site dhcpomapialgorithm=hmac-sha256 + dnssec-keygen -a HMAC-SHA256 -b 128 -n host xcat_key + +If your DNS provider requires a specific TSIG key name, set **dhcpomapikeyname** and store the secret under the matching **passwd** entry: :: + + chdef -t site dhcpomapikeyname=mydnskey + chtab key=omapi username=mydnskey passwd.password="" + +If a legacy ISC DHCP deployment uses an alternate ISC build, **dhcpomshellpath** can point xCAT at that build's ``omshell`` binary: :: + + chdef -t site dhcpomshellpath=/opt/dhcp/bin/omshell + +After changing these values, rerun ``makedns`` and ``makedhcp`` so the generated DNS and DHCP configuration files use the same key settings. + Option #1: Running DNS on Your Management Node ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -499,4 +520,3 @@ Execute ``confignetwork -s`` to configure provision IP address as static IP addr updatenode cn1 -P "confignetwork -s" - diff --git a/docs/source/guides/admin-guides/references/man5/site.5.rst b/docs/source/guides/admin-guides/references/man5/site.5.rst index 4d890f22f..bf93eb98e 100644 --- a/docs/source/guides/admin-guides/references/man5/site.5.rst +++ b/docs/source/guides/admin-guides/references/man5/site.5.rst @@ -92,6 +92,21 @@ site Attributes: ------------- DHCP ATTRIBUTES ------------- + dhcpomapialgorithm: The TSIG/OMAPI algorithm used by legacy ISC DHCP and + BIND DDNS integration. Valid values are hmac-md5, + hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384, + and hmac-sha512. The default is hmac-md5 for + compatibility with existing ISC DHCP installations. + + dhcpomapikeyname: The TSIG/OMAPI key name used by legacy ISC DHCP and + BIND DDNS integration. The default is xcat_key. The + value maps to the passwd table entry where key=omapi + and username is the selected key name. + + dhcpomshellpath: The absolute path to the omshell binary used by legacy + ISC DHCP live OMAPI updates. The default is + /usr/bin/omshell. + dhcpinterfaces: The network interfaces DHCP should listen on. If it is the same for all nodes, use a comma-separated list of the NICs. To specify different NICs for different nodes, use the format: "xcatmn|eth1,eth2;service|bond0", diff --git a/docs/source/guides/admin-guides/references/man8/makedhcp.8.rst b/docs/source/guides/admin-guides/references/man8/makedhcp.8.rst index 9e586e09f..79f2b2ef0 100644 --- a/docs/source/guides/admin-guides/references/man8/makedhcp.8.rst +++ b/docs/source/guides/admin-guides/references/man8/makedhcp.8.rst @@ -43,6 +43,10 @@ The \ **makedhcp**\ command creates and updates the DHCP configuration on the m The \ **makedhcp**\ command is supported for both Linux and AIX clusters. On Linux, the DHCP implementation is selected by the ``site.dhcpbackend`` attribute. The ``auto`` setting keeps ISC DHCP on platforms where it is still available and uses Kea DHCP on platforms such as EL10 and Ubuntu 24.04. +For legacy ISC DHCP deployments that need a non-default OMAPI/TSIG key, use +``site.dhcpomapialgorithm`` and ``site.dhcpomapikeyname``. If the system uses +an alternate ISC DHCP build, ``site.dhcpomshellpath`` can point xCAT at that +build's ``omshell`` binary. 1. diff --git a/perl-xCAT/xCAT/DHCP/OmapiPolicy.pm b/perl-xCAT/xCAT/DHCP/OmapiPolicy.pm new file mode 100644 index 000000000..36b2b6d77 --- /dev/null +++ b/perl-xCAT/xCAT/DHCP/OmapiPolicy.pm @@ -0,0 +1,132 @@ +package xCAT::DHCP::OmapiPolicy; + +use strict; +use warnings; + +my %ALGORITHMS = ( + 'hmac-md5' => 157, + 'hmac-sha1' => 161, + 'hmac-sha224' => 162, + 'hmac-sha256' => 163, + 'hmac-sha384' => 164, + 'hmac-sha512' => 165, +); + +sub settings { + my ( $class, %args ) = @_; + + my $raw_algorithm = _site_value( 'dhcpomapialgorithm', %args ); + my $algorithm_explicit = defined($raw_algorithm) && $raw_algorithm ne ''; + my $algorithm = $class->normalize_algorithm($raw_algorithm); + unless ($algorithm) { + return { + error => "Invalid site.dhcpomapialgorithm value '$raw_algorithm'. Valid values are: " + . join( ', ', sort keys %ALGORITHMS ) + . ".", + }; + } + + my $raw_key_name = _site_value( 'dhcpomapikeyname', %args ); + my $key_name = $class->normalize_key_name($raw_key_name); + unless ($key_name) { + return { + error => "Invalid site.dhcpomapikeyname value '$raw_key_name'. Use letters, digits, underscore, dot, or dash.", + }; + } + + my $raw_omshell_path = _site_value( 'dhcpomshellpath', %args ); + my $omshell_path = $class->normalize_omshell_path($raw_omshell_path); + unless ($omshell_path) { + return { + error => "Invalid site.dhcpomshellpath value '$raw_omshell_path'. Use an absolute path without whitespace.", + }; + } + + return { + algorithm => $algorithm, + algorithm_explicit => $algorithm_explicit, + key_name => $key_name, + key_name_for_regex => quotemeta($key_name), + key_rr_type => $ALGORITHMS{$algorithm}, + omshell_path => $omshell_path, + needs_omshell_key_algorithm => $algorithm ne 'hmac-md5', + }; +} + +sub normalize_algorithm { + my ( $class, $algorithm ) = @_; + + $algorithm = 'hmac-md5' unless defined($algorithm) && $algorithm ne ''; + $algorithm =~ s/^\s+|\s+$//g; + $algorithm = lc($algorithm); + + return $algorithm if $ALGORITHMS{$algorithm}; + return; +} + +sub normalize_key_name { + my ( $class, $key_name ) = @_; + + $key_name = 'xcat_key' unless defined($key_name) && $key_name ne ''; + $key_name =~ s/^\s+|\s+$//g; + + return $key_name if $key_name =~ /\A[A-Za-z0-9_][A-Za-z0-9_.-]*\z/; + return; +} + +sub normalize_omshell_path { + my ( $class, $path ) = @_; + + $path = '/usr/bin/omshell' unless defined($path) && $path ne ''; + $path =~ s/^\s+|\s+$//g; + + return $path if $path =~ m{\A/[A-Za-z0-9_.:/%+=@-]+\z}; + return; +} + +sub key_owner { + my ( $class, $settings ) = @_; + + my $owner = $settings->{key_name}; + $owner .= '.' unless $owner =~ /\.\z/; + return $owner; +} + +sub omshell_preamble { + my ( $class, $settings, %args ) = @_; + + my $secret = $args{secret}; + my $commands = ''; + $commands .= "port $args{port}\n" if defined $args{port}; + + # Stock legacy omshell accepts the implicit MD5 default but rejects an + # explicit key-algorithm command, so emit it only when needed. + $commands .= "key-algorithm $settings->{algorithm}\n" + if $settings->{needs_omshell_key_algorithm}; + $commands .= "key $settings->{key_name} \"$secret\"\n"; + $commands .= "server $args{server}\n" + if defined( $args{server} ) && $args{server} ne ''; + return $commands; +} + +sub _site_value { + my ( $key, %args ) = @_; + + if ( ref( $args{site_values} ) eq 'HASH' + && exists $args{site_values}{$key} ) + { + return $args{site_values}{$key}; + } + + return $::XCATSITEVALS{$key} if exists $::XCATSITEVALS{$key}; + + my $value = eval { + require xCAT::TableUtils; + my @entries = xCAT::TableUtils->get_site_attribute($key); + return $entries[0]; + }; + + return $value; +} + +1; diff --git a/perl-xCAT/xCAT/Schema.pm b/perl-xCAT/xCAT/Schema.pm index 10098ed31..cca78aa90 100644 --- a/perl-xCAT/xCAT/Schema.pm +++ b/perl-xCAT/xCAT/Schema.pm @@ -1042,6 +1042,18 @@ passed as argument rather than by table value', " reservations use JSON render and reload unless Control\n" . " Agent operations are explicitly enabled and the Kea\n" . " host-commands hook is installed.\n\n" . +" dhcpomapialgorithm: The TSIG/OMAPI algorithm used by legacy ISC DHCP and\n" . +" BIND DDNS integration. Valid values are hmac-md5,\n" . +" hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384,\n" . +" and hmac-sha512. The default is hmac-md5 for\n" . +" compatibility with existing ISC DHCP installations.\n\n" . +" dhcpomapikeyname: The TSIG/OMAPI key name used by legacy ISC DHCP and\n" . +" BIND DDNS integration. The default is xcat_key. The\n" . +" value maps to the passwd table entry where key=omapi\n" . +" and username is the selected key name.\n\n" . +" dhcpomshellpath: The absolute path to the omshell binary used by legacy\n" . +" ISC DHCP live OMAPI updates. The default is\n" . +" /usr/bin/omshell.\n\n" . " dhcpinterfaces: The network interfaces DHCP should listen on. If it is the same for all\n" . " nodes, use a comma-separated list of the NICs. To specify different NICs\n" . " for different nodes, use the format: \"xcatmn|eth1,eth2;service|bond0\", \n" . diff --git a/xCAT-server/lib/xcat/plugins/ddns.pm b/xCAT-server/lib/xcat/plugins/ddns.pm index df17749bf..411046109 100644 --- a/xCAT-server/lib/xcat/plugins/ddns.pm +++ b/xCAT-server/lib/xcat/plugins/ddns.pm @@ -4,6 +4,7 @@ use Getopt::Long; use Net::DNS; use File::Path; use xCAT::Table; +use xCAT::DHCP::OmapiPolicy; use Sys::Hostname; use xCAT::TableUtils; use xCAT::NetworkUtils qw/getipaddr/; @@ -26,28 +27,50 @@ my $service = "named"; my $ddns_key_path = "/etc/xcat/ddns.key"; # Net::DNS >= 1.36 removed support for sign_tsig($keyname, $secret) and now -# expects a keyfile. Keep the keyfile in sync with the xcat_key secret. +# expects a keyfile. Keep the keyfile in sync with the xCAT OMAPI secret. sub ddns_tsig_algorithm { my ($ctx) = @_; - # Older Net::DNS releases used by Ubuntu 22.04 cannot sign updates with the - # keyfile API, so keep the generated xCAT key on the legacy algorithm that - # both old and new Net::DNS can use. - return "hmac-md5" if (Net::DNS->VERSION < 1.36); - return $ctx->{tsig_algorithm} || "hmac-md5"; + my $settings = $ctx->{omapi_settings} || xCAT::DHCP::OmapiPolicy->settings(); + + # Keep old Net::DNS on MD5 unless the administrator explicitly selects a + # different OMAPI algorithm. Old Net::DNS can sign non-MD5 updates only + # through a KEY RR, which ddns_sign_update builds below. + return "hmac-md5" if (Net::DNS->VERSION < 1.36 && !$settings->{algorithm_explicit}); + return $ctx->{tsig_algorithm} || $settings->{algorithm}; } sub ddns_key_contents { my ($ctx) = @_; + my $settings = $ctx->{omapi_settings} || xCAT::DHCP::OmapiPolicy->settings(); my $algorithm = ddns_tsig_algorithm($ctx); return - "key \"xcat_key\" {\n" + "key \"$settings->{key_name}\" {\n" . "\talgorithm $algorithm;\n" . "\tsecret \"" . $ctx->{privkey} . "\";\n" . "};\n\n"; } +sub ddns_sign_update { + my ($ctx, $update) = @_; + + my $settings = $ctx->{omapi_settings} || xCAT::DHCP::OmapiPolicy->settings(); + if (Net::DNS->VERSION >= 1.36) { + $update->sign_tsig($ddns_key_path); + return; + } + + if ($settings->{algorithm} eq 'hmac-md5') { + $update->sign_tsig($settings->{key_name}, $ctx->{privkey}); + return; + } + + my $owner = xCAT::DHCP::OmapiPolicy->key_owner($settings); + my $keyrr = Net::DNS::RR->new("$owner IN KEY 512 3 $settings->{key_rr_type} $ctx->{privkey}"); + $update->sign_tsig($keyrr); +} + sub ensure_ddns_key_file { my ($ctx) = @_; @@ -268,6 +291,13 @@ sub process_request { my $slave = 0; my $VERBOSE; + $ctx->{omapi_settings} = xCAT::DHCP::OmapiPolicy->settings(); + if ($ctx->{omapi_settings}->{error}) { + xCAT::SvrUtils::sendmsg([ 1, $ctx->{omapi_settings}->{error} ], $callback); + umask($oldmask); + return; + } + # Since the mandatory rpm perl-Net-DNS for makedns on sles12 (perl-Net-DNS-0.73-1.28) has a bug, # user has to update it to a newer version my @rpminfo = `rpm -qi perl-Net-DNS`; @@ -631,7 +661,7 @@ sub process_request { } } my $passtab = xCAT::Table->new('passwd'); - my $pent = $passtab->getAttribs({ key => 'omapi', username => 'xcat_key' }, ['password']); + my $pent = $passtab->getAttribs({ key => 'omapi', username => $ctx->{omapi_settings}->{key_name} }, ['password']); if ($pent and $pent->{password}) { $ctx->{privkey} = $pent->{password}; } #do not warn/error here yet, if we can't generate or extract, we'll know later @@ -1169,6 +1199,9 @@ sub update_namedconf { my $gotoptions = 0; my $gotkey = 0; my %didzones; + my $omapi_settings = $ctx->{omapi_settings} || xCAT::DHCP::OmapiPolicy->settings(); + my $omapi_key_name = $omapi_settings->{key_name}; + my $omapi_key_re = $omapi_settings->{key_name_for_regex}; if (-r $namedlocation) { my @currnamed = (); @@ -1245,7 +1278,7 @@ sub update_namedconf { $i++; $line = $currnamed[$i]; push @candidate, $line; - if ($line =~ /key\s+\"?xcat_key\"?\b/) { + if ($line =~ /key\s+\"?$omapi_key_re\"?(?=\s|;|\{)/) { $needreplace = 0; } } while ($line !~ /^\};/); #skip the old file zone @@ -1254,7 +1287,7 @@ sub update_namedconf { next; } $ctx->{restartneeded} = 1; - push @newnamed, "zone \"$currzone\" in {\n", "\ttype master;\n", "\tallow-update {\n", "\t\tkey xcat_key;\n"; + push @newnamed, "zone \"$currzone\" in {\n", "\ttype master;\n", "\tallow-update {\n", "\t\tkey $omapi_key_name;\n"; my @list; if (not $ctx->{adzones}->{$currzone}) { if ($ctx->{dnsupdaters}) { @@ -1290,7 +1323,7 @@ sub update_namedconf { } while ($line !~ /^\};/); } - } elsif ($line =~ /^key\s+\"?xcat_key\"?\b/) { + } elsif ($line =~ /^key\s+\"?$omapi_key_re\"?(?=\s|\{)/) { $gotkey = 1; my $algorithmnow; if ($ctx->{privkey}) { @@ -1303,7 +1336,13 @@ sub update_namedconf { } push @keyblock, $line; } while ($line !~ /^\};/); - if ($algorithmnow && Net::DNS->VERSION < 1.36 && lc($algorithmnow) ne "hmac-md5") { + if ($omapi_settings->{algorithm_explicit} + && (!$algorithmnow || lc($algorithmnow) ne $omapi_settings->{algorithm}) ) + { + $ctx->{tsig_algorithm} = $omapi_settings->{algorithm}; + push @newnamed, ddns_key_contents($ctx); + $ctx->{restartneeded} = 1; + } elsif ($algorithmnow && Net::DNS->VERSION < 1.36 && lc($algorithmnow) ne "hmac-md5") { $ctx->{tsig_algorithm} = "hmac-md5"; push @newnamed, ddns_key_contents($ctx); $ctx->{restartneeded} = 1; @@ -1317,7 +1356,7 @@ sub update_namedconf { $algorithmnow = $1; } elsif ($line =~ /secret \"([^"]*)\"/) { my $passtab = xCAT::Table->new("passwd", -create => 1); - $passtab->setAttribs({ key => "omapi", username => "xcat_key" }, { password => $1 }); + $passtab->setAttribs({ key => "omapi", username => $omapi_key_name }, { password => $1 }); $ctx->{privkey} = $1; } $i++; @@ -1325,7 +1364,7 @@ sub update_namedconf { push @newnamed, $line; } } - if ($algorithmnow) { + if ($algorithmnow && !$omapi_settings->{algorithm_explicit}) { $ctx->{tsig_algorithm} = $algorithmnow; } } elsif ($line !~ /generated by xCAT/) { @@ -1447,7 +1486,7 @@ sub update_namedconf { push @newnamed, "\ttype slave;\n"; push @newnamed, "\tmasters { $output[0]; };\n"; } else { - push @newnamed, "\ttype master;\n", "\tallow-update {\n", "\t\tkey xcat_key;\n", "\t};\n"; + push @newnamed, "\ttype master;\n", "\tallow-update {\n", "\t\tkey $omapi_key_name;\n", "\t};\n"; foreach (@{ $ctx->{dnsupdaters} }) { push @newnamed, "\t\t$_;\n"; } @@ -1474,7 +1513,7 @@ sub update_namedconf { push @newnamed, "\ttype slave;\n"; push @newnamed, "\tmasters { $output[0]; };\n"; } else { - push @newnamed, "\ttype master;\n", "\tallow-update {\n", "\t\tkey xcat_key;\n"; + push @newnamed, "\ttype master;\n", "\tallow-update {\n", "\t\tkey $omapi_key_name;\n"; foreach (@{ $ctx->{adservers} }) { push @newnamed, "\t\t$_;\n"; } @@ -1537,13 +1576,14 @@ sub add_or_delete_records { unless ($ctx->{privkey}) { my $passtab = xCAT::Table->new('passwd'); - my $pent = $passtab->getAttribs({ key => 'omapi', username => 'xcat_key' }, ['password']); + my $pent = $passtab->getAttribs({ key => 'omapi', username => $ctx->{omapi_settings}->{key_name} }, ['password']); if ($pent and $pent->{password}) { $ctx->{privkey} = $pent->{password}; } else { xCAT::SvrUtils::sendmsg([ 1, "Unable to find omapi key in passwd table" ], $callback); } } + ensure_ddns_key_file($ctx) if $ctx->{privkey}; my $node; my @ips; @@ -1635,13 +1675,9 @@ sub add_or_delete_records { $numreqs -= 1; if ($numreqs == 0) { - # sometimes even the xcat_key is correct, but named still replies NOTAUTH, so retry + # sometimes even the key is correct, but named still replies NOTAUTH, so retry for (1 .. 3) { - if (Net::DNS->VERSION >= 1.36) { - $update->sign_tsig($ddns_key_path); - } else { - $update->sign_tsig("xcat_key", $ctx->{privkey}); - } + ddns_sign_update($ctx, $update); $numreqs = 300; my $reply = $resolver->send($update); if ($reply) { @@ -1661,13 +1697,9 @@ sub add_or_delete_records { } } if ($numreqs != 300) { #either no entries at all to begin with or a perfect multiple of 300 - # sometimes even the xcat_key is correct, but named still replies NOTAUTH, so retry + # sometimes even the key is correct, but named still replies NOTAUTH, so retry for (1 .. 3) { - if (Net::DNS->VERSION >= 1.36) { - $update->sign_tsig($ddns_key_path); - } else { - $update->sign_tsig("xcat_key", $ctx->{privkey}); - } + ddns_sign_update($ctx, $update); my $reply = $resolver->send($update); if ($reply) { if ($reply->header->rcode eq 'NOTAUTH') { diff --git a/xCAT-server/lib/xcat/plugins/dhcp.pm b/xCAT-server/lib/xcat/plugins/dhcp.pm index de784fe9f..ac975752c 100644 --- a/xCAT-server/lib/xcat/plugins/dhcp.pm +++ b/xCAT-server/lib/xcat/plugins/dhcp.pm @@ -11,8 +11,10 @@ use strict; use IPC::Open2; use IPC::Open3; use IO::Select; +use File::Temp qw(tempfile); use Symbol qw/gensym/; use POSIX qw/WNOHANG/; +use Time::HiRes qw(sleep); use xCAT::Table; #use Data::Dumper; @@ -31,6 +33,7 @@ use IPC::Open2; use xCAT::Utils; use xCAT::DHCP::BootPolicy; use xCAT::DHCP::Backend; +use xCAT::DHCP::OmapiPolicy; use xCAT::DHCP::Range; use xCAT::TableUtils; use xCAT::NetworkUtils qw/getipaddr/; @@ -47,6 +50,8 @@ my $site_domain; my @alldomains; my $omshell; my $omshell6; #separate session to DHCPv6 instance of dhcp +my $omshellpid; +my $omshell6pid; my $statements; #Hold custom statements to be slipped into host declarations my $localonly; # flag for running only on local server - needs to be global my $callback; @@ -153,6 +158,167 @@ sub handled_commands return { makedhcp => "dhcp", }; } +sub _omapi_settings +{ + my $cb = shift || $callback; + + my $settings = xCAT::DHCP::OmapiPolicy->settings(); + if ($settings->{error}) { + $cb->({ error => [ $settings->{error} ], errorcode => [1] }) if $cb; + syslog("local4|err", $settings->{error}); + return; + } + + return $settings; +} + +sub _omapi_passwd_entry +{ + my $settings = shift; + + my $passtab = xCAT::Table->new('passwd', -create => 1); + return unless $passtab; + return $passtab->getAttribs({ key => 'omapi', username => $settings->{key_name} }, qw(username password)); +} + +sub _ubuntu_isc_omapi_limited +{ + # Ubuntu's ISC DHCP 4.4 omshell can hang or crash on failed host opens + # and on next-server host statements. Use static local host blocks there. + return $distro =~ /^ubuntu(20|20\.04|22|22\.04)/; +} + +sub _omapi_ip_lookup_supported +{ + return !_ubuntu_isc_omapi_limited(); +} + +sub _omapi_pre_create_cleanup_supported +{ + return !_ubuntu_isc_omapi_limited(); +} + +sub _omapi_next_server_statement +{ + my $server = shift; + + return '' if _ubuntu_isc_omapi_limited(); + return 'next-server ' . $server . ';'; +} + +sub _isc_static_host_fallback +{ + return _ubuntu_isc_omapi_limited() && !$::XCATSITEVALS{externaldhcpservers}; +} + +sub _delete_isc_static_host +{ + my $node = shift; + + my @updated; + my $skip = 0; + foreach my $line (@dhcpconf) { + if ($line =~ /^#xCAT host declaration for \Q$node\E\b.* start$/) { + $skip = 1; + next; + } + if ($skip && $line =~ /^#xCAT host declaration for \Q$node\E\b.* end$/) { + $skip = 0; + next; + } + push @updated, $line unless $skip; + } + @dhcpconf = @updated; +} + +sub _static_host_statements +{ + my $statements = shift || ''; + + $statements =~ s/ddns-hostname \\"([^"]+)\\";/ddns-hostname "$1";/g; + $statements =~ s/send host-name \\"([^"]+)\\";/option host-name "$1";/g; + $statements =~ s/\\"/"/g; + + return $statements; +} + +sub _add_isc_static_host +{ + my ($node, $hostname, $mac, $ip, $statements) = @_; + + return unless $ip && $ip ne 'DENIED'; + + _delete_isc_static_host($node); + + my $host_statements = _static_host_statements($statements); + push @dhcpconf, "#xCAT host declaration for $node aka host $hostname start\n"; + push @dhcpconf, "host $hostname {\n"; + push @dhcpconf, " hardware ethernet $mac;\n"; + push @dhcpconf, " fixed-address $ip;\n"; + push @dhcpconf, " $host_statements\n" if $host_statements; + push @dhcpconf, "} #xCAT host declaration for $node aka host $hostname end\n"; + + $restartdhcp = 1; +} + +sub _open_omshell_writer +{ + my $settings = shift; + + mkdir "/tmp/xcat" unless -d "/tmp/xcat"; + my ($omshell_stdin, $command_file) = tempfile('omshell.XXXXXX', DIR => '/tmp/xcat', UNLINK => 0); + return unless $omshell_stdin; + + return ($omshell_stdin, { command_file => $command_file, omshell_path => $settings->{omshell_path} }); +} + +sub _run_omshell_command_file +{ + my ($command_file, $omshell_path) = @_; + + my $pid = fork(); + return unless defined $pid; + + if ($pid == 0) { + open(STDIN, '<', $command_file) or exit 127; ## no critic (InputOutput::RequireCheckedOpen) + open(STDOUT, '>', '/dev/null') or exit 127; ## no critic (InputOutput::RequireCheckedOpen) + open(STDERR, '>', '/dev/null') or exit 127; ## no critic (InputOutput::RequireCheckedOpen) + exec { $omshell_path } $omshell_path; + exit 127; + } + + for (1 .. 100) { + if (waitpid($pid, WNOHANG) == $pid) { + sleep 1.0; + return 1; + } + sleep 0.1; + } + + kill 'TERM', $pid; + for (1 .. 20) { + return if waitpid($pid, WNOHANG) == $pid; + sleep 0.1; + } + + kill 'KILL', $pid; + waitpid($pid, 0); + return; +} + +sub _close_omshell_writer +{ + my ($fh, $writer) = @_; + + close($fh) if $fh; + + return unless ref($writer) eq 'HASH'; + + my $ok = _run_omshell_command_file($writer->{command_file}, $writer->{omshell_path}); + unlink $writer->{command_file}; + syslog("local4|err", "omshell did not complete while updating DHCP reservations") unless $ok; +} + ###################################################### # Run omshell to query a host and parse the output. # Uses a heredoc pipe instead of open2 to avoid deadlock @@ -160,15 +326,15 @@ sub handled_commands ###################################################### sub _omshell_query_host { - my $node = shift; - my $omapiuser = shift; - my $omapikey = shift; - my $port = shift; + my $node = shift; + my $settings = shift; + my $omapikey = shift; + my $port = shift; - my $port_cmd = defined($port) ? "port $port\n" : ""; - my $omcmds = "${port_cmd}key $omapiuser \"$omapikey\"\nconnect\nnew host\nset name = \"$node\"\nopen\nclose\n"; + my $omcmds = xCAT::DHCP::OmapiPolicy->omshell_preamble($settings, secret => $omapikey, port => $port); + $omcmds .= "connect\nnew host\nset name = \"$node\"\nopen\nclose\n"; - my @output = _run_omshell($omcmds); + my @output = _run_omshell($omcmds, $settings); return _parse_omshell_host_output($node, @output); } @@ -176,10 +342,12 @@ sub _omshell_query_host sub _run_omshell { my $omcmds = shift; + my $settings = shift || _omapi_settings(); + return () unless $settings; my ($in, $out); my $err = gensym; - my $pid = eval { open3($in, $out, $err, '/usr/bin/omshell') }; + my $pid = eval { open3($in, $out, $err, $settings->{omshell_path}) }; return () if $@ || !$pid; print $in $omcmds; @@ -208,7 +376,7 @@ sub _run_omshell for (1 .. 10) { last if waitpid($pid, WNOHANG) == $pid; - select(undef, undef, undef, 0.1); + sleep 0.1; } if (waitpid($pid, WNOHANG) == 0) { kill 'KILL', $pid; @@ -253,17 +421,15 @@ sub listnode my $callback = shift; my $rsp; - my $omapiuser; - my $omapikey; + my $settings = _omapi_settings($callback); + return unless $settings; - my $pwtab = xCAT::Table->new("passwd"); - my @pws = $pwtab->getAllAttribs('key', 'username', 'password', 'cryptmethod', 'authdomain', 'comments', 'disable'); - foreach (@pws) { - if ($_->{key} =~ "omapi") { - $omapiuser = $_->{username}; - $omapikey = $_->{password}; - } + my $pent = _omapi_passwd_entry($settings); + unless ($pent && $pent->{password}) { + $callback->({ error => ["Unable to access omapi key from passwd table, add the key from dhcpd.conf or makedhcp -n to create a new one"], errorcode => [1] }); + return; } + my $omapikey = $pent->{password}; my $usingipv6; my $nettab = xCAT::Table->new("networks"); @@ -274,14 +440,14 @@ sub listnode } } - my ($nname, $ipaddr, $hwaddr) = _omshell_query_host($node, $omapiuser, $omapikey, undef); + my ($nname, $ipaddr, $hwaddr) = _omshell_query_host($node, $settings, $omapikey, undef); if ($ipaddr) { push @{ $rsp->{data} }, "$nname: $ipaddr, $hwaddr"; xCAT::MsgUtils->message("I", $rsp, $callback); } if ($usingipv6) { - my ($nname6, $ipaddr6, $hwaddr6) = _omshell_query_host($node, $omapiuser, $omapikey, 7912); + my ($nname6, $ipaddr6, $hwaddr6) = _omshell_query_host($node, $settings, $omapikey, 7912); if ($ipaddr6) { push @{ $rsp->{data} }, "$nname6: $ipaddr6, $hwaddr6"; xCAT::MsgUtils->message("I", $rsp, $callback); @@ -294,6 +460,12 @@ sub delnode my $node = shift; my $inetn = inet_aton($node); + if (_isc_static_host_fallback()) { + _delete_isc_static_host($node); + $restartdhcp = 1; + return; + } + my $mactab = xCAT::Table->new('mac'); my $ent; if ($machash) { $ent = $machash->{$node}->[0]; } @@ -350,7 +522,7 @@ sub delnode print $omshell "remove\n"; print $omshell "close\n"; } - if ($inetn) + if ($inetn and _omapi_ip_lookup_supported()) { my $ip; if (inet_aton($hostname)) @@ -374,7 +546,7 @@ sub delnode print $omshell "open\n"; print $omshell "remove\n"; print $omshell "close\n"; - if ($inetn) + if ($inetn and _omapi_ip_lookup_supported()) { my $ip = inet_ntoa(inet_aton($node)); unless ($ip) { return; } @@ -513,10 +685,7 @@ sub addnode } $tftpserver = inet_ntoa($tmp_name); $nxtsrv = $tftpserver; - $lstatements = - 'next-server ' - . $tftpserver . ';' - . $statements; + $lstatements = _omapi_next_server_statement($tftpserver) . $statements; } else { @@ -772,26 +941,45 @@ sub addnode $hardwaretype = 32; } + if (_isc_static_host_fallback()) { + if ($ip ne "DENIED") { + if ($lstatements) { + $lstatements = 'ddns-hostname \"' . $node . '\"; send host-name \"' . $node . '\";' . $lstatements; + } else { + $lstatements = 'ddns-hostname \"' . $node . '\"; send host-name \"' . $node . '\";'; + } + } else { + $lstatements = "deny booting;"; + } + _add_isc_static_host($node, $hostname, $mac, $ip, $lstatements); + $count = $count + 2; + next; + } + #syslog("local4|err", "Setting $node ($hname|$ip) to " . $mac); - print $omshell "new host\n"; - print $omshell - "set name = \"$hostname\"\n"; #Find and destroy conflict name - print $omshell "open\n"; - print $omshell "remove\n"; - print $omshell "close\n"; - if ($ip and $ip ne 'DENIED') { + if (_omapi_pre_create_cleanup_supported()) { + print $omshell "new host\n"; + print $omshell + "set name = \"$hostname\"\n"; #Find and destroy conflict name + print $omshell "open\n"; + print $omshell "remove\n"; + print $omshell "close\n"; + } + if ($ip and $ip ne 'DENIED' and _omapi_ip_lookup_supported()) { print $omshell "new host\n"; print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict print $omshell "open\n"; print $omshell "remove\n"; print $omshell "close\n"; } - print $omshell "new host\n"; - print $omshell "set hardware-address = " . $mac - . "\n"; #find and destroy mac conflict - print $omshell "open\n"; - print $omshell "remove\n"; - print $omshell "close\n"; + if (_omapi_pre_create_cleanup_supported()) { + print $omshell "new host\n"; + print $omshell "set hardware-address = " . $mac + . "\n"; #find and destroy mac conflict + print $omshell "open\n"; + print $omshell "remove\n"; + print $omshell "close\n"; + } print $omshell "new host\n"; print $omshell "set name = \"$hostname\"\n"; print $omshell "set hardware-address = " . $mac . "\n"; @@ -1988,12 +2176,13 @@ sub process_request } } - if ($^O ne 'aix') + if ($^O ne 'aix' and !_isc_static_host_fallback()) { - my $passtab = xCAT::Table->new('passwd'); - my $ent; - ($ent) = $passtab->getAttribs({ key => "omapi" }, qw(username password)); - unless ($ent->{username} and $ent->{password}) + my $settings = _omapi_settings(); + return unless $settings; + + my $ent = _omapi_passwd_entry($settings); + unless ($ent && $ent->{username} && $ent->{password}) { $callback->({ error => ["Unable to access omapi key from passwd table, add the key from dhcpd.conf or makedhcp -n to create a new one"], errorcode => [1] }); syslog("local4|err", "Unable to access omapi key from passwd table, unable to update DHCP configuration"); @@ -2002,23 +2191,28 @@ sub process_request #Have nodes to update #open2($omshellout,$omshell,"/usr/bin/omshell"); - open($omshell, "|/usr/bin/omshell > /dev/null"); - print $omshell "key " - . $ent->{username} . " \"" - . $ent->{password} . "\"\n"; - if ($::XCATSITEVALS{externaldhcpservers}) { - print $omshell "server $::XCATSITEVALS{externaldhcpservers}\n"; + ($omshell, $omshellpid) = _open_omshell_writer($settings); + unless ($omshell) { + $callback->({ error => ["Unable to start $settings->{omshell_path}"], errorcode => [1] }); + syslog("local4|err", "Unable to start $settings->{omshell_path}"); + return; } + print $omshell xCAT::DHCP::OmapiPolicy->omshell_preamble($settings, + secret => $ent->{password}, + server => $::XCATSITEVALS{externaldhcpservers}); print $omshell "connect\n"; if ($usingipv6) { - open($omshell6, "|/usr/bin/omshell > /dev/null"); - if ($::XCATSITEVALS{externaldhcpservers}) { - print $omshell "server $::XCATSITEVALS{externaldhcpservers}\n"; + ($omshell6, $omshell6pid) = _open_omshell_writer($settings); + unless ($omshell6) { + $callback->({ error => ["Unable to start $settings->{omshell_path}"], errorcode => [1] }); + syslog("local4|err", "Unable to start $settings->{omshell_path}"); + _close_omshell_writer($omshell, $omshellpid); + return; } - print $omshell6 "port 7912\n"; - print $omshell6 "key " - . $ent->{username} . " \"" - . $ent->{password} . "\"\n"; + print $omshell6 xCAT::DHCP::OmapiPolicy->omshell_preamble($settings, + secret => $ent->{password}, + port => 7912, + server => $::XCATSITEVALS{externaldhcpservers}); print $omshell6 "connect\n"; } } @@ -2079,8 +2273,8 @@ sub process_request } } } - close($omshell) if ($^O ne 'aix'); - close($omshell6) if ($omshell6 and $^O ne 'aix'); + _close_omshell_writer($omshell, $omshellpid) if ($^O ne 'aix'); + _close_omshell_writer($omshell6, $omshell6pid) if ($omshell6 and $^O ne 'aix'); } writeout(); if (not $::XCATSITEVALS{externaldhcpservers} and $restartdhcp) { @@ -3589,17 +3783,20 @@ sub addnet6 $ddnsdomain = $netcfgs{$net}->{ddnsdomain}; } if ($::XCATSITEVALS{dnshandler} =~ /ddns/) { + my $settings = _omapi_settings(); + return 1 unless $settings; + if ($ddnsdomain) { push @netent, " ddns-domainname \"" . $ddnsdomain . "\";\n"; push @netent, " zone $ddnsdomain. {\n"; } else { push @netent, " zone $netdomain. {\n"; } - push @netent, " primary $ddnserver; key xcat_key; \n"; + push @netent, " primary $ddnserver; key $settings->{key_name}; \n"; push @netent, " }\n"; foreach (getzonesfornet($net)) { push @netent, " zone $_ {\n"; - push @netent, " primary $ddnserver; key xcat_key; \n"; + push @netent, " primary $ddnserver; key $settings->{key_name}; \n"; push @netent, " }\n"; } } @@ -3915,6 +4112,9 @@ sub addnet $ddnsdomain = $netcfgs{$net}->{ddnsdomain}; } if ($::XCATSITEVALS{dnshandler} =~ /ddns/) { + my $settings = _omapi_settings(); + return 1 unless $settings; + if ($ddnsdomain) { push @netent, " ddns-domainname \"" . $ddnsdomain . "\";\n"; push @netent, " zone $ddnsdomain. {\n"; @@ -3923,14 +4123,14 @@ sub addnet } if ($ddnserver) { - push @netent, " primary $ddnserver; key xcat_key; \n"; + push @netent, " primary $ddnserver; key $settings->{key_name}; \n"; } push @netent, " }\n"; foreach (getzonesfornet($net, $mask)) { push @netent, " zone $_ {\n"; if ($ddnserver) { - push @netent, " primary $ddnserver; key xcat_key; \n"; + push @netent, " primary $ddnserver; key $settings->{key_name}; \n"; } push @netent, " }\n"; } @@ -4178,6 +4378,9 @@ sub writeout sub newconfig6 { if ($::XCATSITEVALS{externaldhcpservers}) { return; } + my $settings = _omapi_settings(); + return 1 unless $settings; + #phase 1, basic working #phase 2, ddns too, evaluate other stuff from dhcpv4 as applicable push @dhcp6conf, "#xCAT generated dhcp configuration\n"; @@ -4187,14 +4390,14 @@ sub newconfig6 { # push @dhcp6conf, "update-static-leases on;\n"; push @dhcp6conf, "omapi-port 7912;\n"; #Enable omapi... - push @dhcp6conf, "key xcat_key {\n"; - push @dhcp6conf, " algorithm hmac-md5;\n"; + push @dhcp6conf, "key $settings->{key_name} {\n"; + push @dhcp6conf, " algorithm $settings->{algorithm};\n"; my $passtab = xCAT::Table->new('passwd', -create => 1); (my $passent) = - $passtab->getAttribs({ key => 'omapi', username => 'xcat_key' }, 'password'); + $passtab->getAttribs({ key => 'omapi', username => $settings->{key_name} }, 'password'); my $secret = encode_base64(genpassword(32)); #Random from set of 62^32 chomp $secret; - if ($passent->{password}) { $secret = $passent->{password}; } + if ($passent && $passent->{password}) { $secret = $passent->{password}; } else { $callback->( @@ -4203,13 +4406,13 @@ sub newconfig6 { ["The dhcp server must be restarted for OMAPI function to work"] } ); - $passtab->setAttribs({ key => 'omapi' }, - { username => 'xcat_key', password => $secret }); + $passtab->setAttribs({ key => 'omapi', username => $settings->{key_name} }, + { username => $settings->{key_name}, password => $secret }); } push @dhcp6conf, " secret \"" . $secret . "\";\n"; push @dhcp6conf, "};\n"; - push @dhcp6conf, "omapi-key xcat_key;\n"; + push @dhcp6conf, "omapi-key $settings->{key_name};\n"; #that is all for pristine ipv6 config } @@ -4219,6 +4422,9 @@ sub newconfig if ($::XCATSITEVALS{externaldhcpservers}) { return; } return newconfig_aix() if ($^O eq 'aix'); + my $settings = _omapi_settings(); + return 1 unless $settings; + # This function puts a standard header in and enough to make omapi work. my $passtab = xCAT::Table->new('passwd', -create => 1); push @dhcpconf, "#xCAT generated dhcp configuration\n"; @@ -4250,13 +4456,13 @@ sub newconfig push @dhcpconf, "option cumulus-provision-url code 239 = text;\n"; push @dhcpconf, "\n"; push @dhcpconf, "omapi-port 7911;\n"; #Enable omapi... - push @dhcpconf, "key xcat_key {\n"; - push @dhcpconf, " algorithm hmac-md5;\n"; + push @dhcpconf, "key $settings->{key_name} {\n"; + push @dhcpconf, " algorithm $settings->{algorithm};\n"; (my $passent) = - $passtab->getAttribs({ key => 'omapi', username => 'xcat_key' }, 'password'); + $passtab->getAttribs({ key => 'omapi', username => $settings->{key_name} }, 'password'); my $secret = encode_base64(genpassword(32)); #Random from set of 62^32 chomp $secret; - if ($passent->{password}) { $secret = $passent->{password}; } + if ($passent && $passent->{password}) { $secret = $passent->{password}; } else { $callback->( @@ -4265,13 +4471,13 @@ sub newconfig ["The dhcp server must be restarted for OMAPI function to work"] } ); - $passtab->setAttribs({ key => 'omapi' }, - { username => 'xcat_key', password => $secret }); + $passtab->setAttribs({ key => 'omapi', username => $settings->{key_name} }, + { username => $settings->{key_name}, password => $secret }); } push @dhcpconf, " secret \"" . $secret . "\";\n"; push @dhcpconf, "};\n"; - push @dhcpconf, "omapi-key xcat_key;\n"; + push @dhcpconf, "omapi-key $settings->{key_name};\n"; push @dhcpconf, ('class "pxe" {' . "\n", " match if substring (option vendor-class-identifier, 0, 9) = \"PXEClient\";\n", " ddns-updates off;\n", " max-lease-time 600;\n", "}\n"); } diff --git a/xCAT-server/share/xcat/tools/dhcpop b/xCAT-server/share/xcat/tools/dhcpop index 814123447..f19435a6f 100755 --- a/xCAT-server/share/xcat/tools/dhcpop +++ b/xCAT-server/share/xcat/tools/dhcpop @@ -10,7 +10,14 @@ BEGIN use lib "$::XCATROOT/lib/perl"; use Getopt::Long; +use File::Temp qw(tempfile); use Fcntl ':flock'; +use IPC::Open3; +use POSIX qw/WNOHANG/; +use Symbol qw/gensym/; +use Time::HiRes qw(sleep); +use xCAT::DHCP::OmapiPolicy; +use xCAT::Table; sub usage{ print "Usage: dhcphelper -h \n"; @@ -97,15 +104,21 @@ if($help){ exit 0; } - my $out=qx(tabdump -w key==omapi -w username==xcat_key passwd |tail -n1|awk -F, '{print \$2","\$3}'); - $out =~ s/("|\n)//g; - my ($id,$passwd)=split(',',$out); - if(-z "$id" || -z "$passwd" ){ + my $settings = xCAT::DHCP::OmapiPolicy->settings(); + if ($settings->{error}) { + print "Error: $settings->{error}\n"; + exit 1; + } + + my $passtab = xCAT::Table->new('passwd'); + my $pent = $passtab->getAttribs({ key => 'omapi', username => $settings->{key_name} }, qw(username password)); + my $passwd = $pent ? $pent->{password} : undef; + if(!$passwd){ print "Error: no 'omapi' entry defined in passwd table!"; exit 1; } - my $omshell_commands = "key " . $id . " \"" . $passwd . "\"\n"; + my $omshell_commands = xCAT::DHCP::OmapiPolicy->omshell_preamble($settings, secret => $passwd); $omshell_commands .= "connect\n"; if($hostname){ @@ -133,11 +146,41 @@ if($help){ $omshell_commands .= "close\n"; } - my $omshell; - open($omshell, '|-', '/bin/sh', '-c', '/usr/bin/omshell >/dev/null') - or die "Unable to start omshell: $!"; + mkdir "/tmp/xcat" unless -d "/tmp/xcat"; + my ($omshell, $command_file) = tempfile('omshell.XXXXXX', DIR => '/tmp/xcat', UNLINK => 0); + die "Unable to start omshell: $!" unless $omshell; print $omshell $omshell_commands; close($omshell); + + my $pid = fork(); + die "Unable to start omshell: $!" unless defined $pid; + if ($pid == 0) { + open(STDIN, '<', $command_file) or exit 127; ## no critic (InputOutput::RequireCheckedOpen) + open(STDOUT, '>', '/dev/null') or exit 127; ## no critic (InputOutput::RequireCheckedOpen) + open(STDERR, '>', '/dev/null') or exit 127; ## no critic (InputOutput::RequireCheckedOpen) + exec { $settings->{omshell_path} } $settings->{omshell_path}; + exit 127; + } + + for (1 .. 100) { + if (waitpid($pid, WNOHANG) == $pid) { + sleep 1.0; + unlink $command_file; + exit 0; + } + sleep 0.1; + } + kill 'TERM', $pid; + for (1 .. 20) { + if (waitpid($pid, WNOHANG) == $pid) { + unlink $command_file; + exit 0; + } + sleep 0.1; + } + kill 'KILL', $pid; + waitpid($pid, 0); + unlink $command_file; }else{ &usage; exit 1; diff --git a/xCAT-test/unit/ddns_omapi_policy.t b/xCAT-test/unit/ddns_omapi_policy.t new file mode 100644 index 000000000..5244eed87 --- /dev/null +++ b/xCAT-test/unit/ddns_omapi_policy.t @@ -0,0 +1,64 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../xCAT-server/lib"; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use lib "$FindBin::Bin/../../perl-xCAT"; + +use Test::More; + +$ENV{XCATCFG} ||= 'SQLite:/tmp'; +$ENV{XCATROOT} ||= "$FindBin::Bin/../../xCAT-server"; + +my $source_ddns_plugin = + "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/ddns.pm"; +if ( -f $source_ddns_plugin ) { + require $source_ddns_plugin; +} +else { + require xCAT_plugin::ddns; +} + +my $defaults = xCAT::DHCP::OmapiPolicy->settings( site_values => {} ); +is( + xCAT_plugin::ddns::ddns_key_contents( + { + omapi_settings => $defaults, + privkey => 'legacy-secret', + } + ), +"key \"xcat_key\" {\n\talgorithm hmac-md5;\n\tsecret \"legacy-secret\";\n};\n\n", + 'default DDNS key remains xcat_key with hmac-md5' +); + +my $sha512 = xCAT::DHCP::OmapiPolicy->settings( + site_values => { + dhcpomapialgorithm => 'hmac-sha512', + dhcpomapikeyname => 'provider.key', + } +); + +is( + xCAT_plugin::ddns::ddns_tsig_algorithm( + { + omapi_settings => $sha512, + } + ), + 'hmac-sha512', + 'explicit non-MD5 DDNS algorithm is honored' +); + +is( + xCAT_plugin::ddns::ddns_key_contents( + { + omapi_settings => $sha512, + privkey => 'provider-secret', + } + ), +"key \"provider.key\" {\n\talgorithm hmac-sha512;\n\tsecret \"provider-secret\";\n};\n\n", + 'custom DDNS key name and algorithm are rendered' +); + +done_testing(); diff --git a/xCAT-test/unit/dhcp_omapi_policy.t b/xCAT-test/unit/dhcp_omapi_policy.t new file mode 100644 index 000000000..15d74e79b --- /dev/null +++ b/xCAT-test/unit/dhcp_omapi_policy.t @@ -0,0 +1,95 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../perl-xCAT"; + +use Test::More; + +use xCAT::DHCP::OmapiPolicy; + +my $defaults = xCAT::DHCP::OmapiPolicy->settings( site_values => {} ); +is( $defaults->{algorithm}, + 'hmac-md5', 'default OMAPI algorithm remains hmac-md5' ); +is( $defaults->{key_name}, 'xcat_key', + 'default OMAPI key name remains xcat_key' ); +is( $defaults->{omshell_path}, + '/usr/bin/omshell', 'default omshell path remains /usr/bin/omshell' ); +ok( + !$defaults->{needs_omshell_key_algorithm}, + 'default MD5 does not emit key-algorithm' +); +is( + xCAT::DHCP::OmapiPolicy->omshell_preamble( + $defaults, secret => 'legacy-secret' + ), + "key xcat_key \"legacy-secret\"\n", + 'default omshell preamble keeps legacy key command without key-algorithm' +); + +my $sha512 = xCAT::DHCP::OmapiPolicy->settings( + site_values => { + dhcpomapialgorithm => ' HMAC-SHA512 ', + dhcpomapikeyname => 'external.key-name', + dhcpomshellpath => '/opt/dhcp/bin/omshell', + } +); +is( $sha512->{algorithm}, 'hmac-sha512', 'algorithm is canonicalized' ); +is( $sha512->{key_rr_type}, 165, 'SHA512 KEY RR type is mapped' ); +is( $sha512->{key_name}, 'external.key-name', 'custom key name is accepted' ); +is( $sha512->{key_name_for_regex}, + 'external\\.key\\-name', + 'custom key name is escaped for named.conf matching' ); +is( $sha512->{omshell_path}, + '/opt/dhcp/bin/omshell', 'custom absolute omshell path is accepted' ); +ok( + $sha512->{needs_omshell_key_algorithm}, + 'non-MD5 emits key-algorithm for omshell' +); +is( + xCAT::DHCP::OmapiPolicy->omshell_preamble( + $sha512, + secret => 'secret==', + port => 7912, + server => '192.0.2.10' + ), +"port 7912\nkey-algorithm hmac-sha512\nkey external.key-name \"secret==\"\nserver 192.0.2.10\n", + 'omshell preamble includes port, algorithm, key, and server in order' +); +is( xCAT::DHCP::OmapiPolicy->key_owner($sha512), + 'external.key-name.', 'DNS key owner is fully qualified' ); + +like( + xCAT::DHCP::OmapiPolicy->settings( + site_values => { dhcpomapialgorithm => 'sha512' } + )->{error}, + qr/site\.dhcpomapialgorithm/, + 'invalid algorithm is rejected' +); + +like( + xCAT::DHCP::OmapiPolicy->settings( + site_values => { dhcpomapikeyname => 'bad;name' } + )->{error}, + qr/site\.dhcpomapikeyname/, + 'unsafe key name is rejected' +); + +like( + xCAT::DHCP::OmapiPolicy->settings( + site_values => { dhcpomshellpath => 'omshell' } + )->{error}, + qr/site\.dhcpomshellpath/, + 'relative omshell path is rejected' +); + +like( + xCAT::DHCP::OmapiPolicy->settings( + site_values => { dhcpomshellpath => '/tmp/omshell;touch' } + )->{error}, + qr/site\.dhcpomshellpath/, + 'shell metacharacters are rejected from omshell path' +); + +done_testing();