2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-05-17 11:54:16 +00:00

Merge pull request #7563 from VersatusHPC/fix/isc-omapi-tsig-policy

feat: support configurable ISC OMAPI TSIG policy
This commit is contained in:
Markus Hilger
2026-05-07 19:51:32 +02:00
committed by GitHub
10 changed files with 738 additions and 115 deletions
@@ -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="<secret from provider>"
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"
@@ -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",
@@ -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.
+132
View File
@@ -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;
+12
View File
@@ -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" .
+61 -29
View File
@@ -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') {
+283 -77
View File
@@ -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");
}
+51 -8
View File
@@ -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;
+64
View File
@@ -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();
+95
View File
@@ -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();