#!/usr/bin/env perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html package xCAT::xcatd; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; } use strict; use xCAT::Table; use xCAT::MsgUtils; use Data::Dumper; use xCAT::NodeRange; use xCAT::Utils; #-------------------------------------------------------------------------------- =head1 xCAT::XCATD =head2 Package Description This program module file, is a set of utilities used by xCAT daemon. =cut #------------------------------------------------------------------------------ =head3 validate Here is where we check if $peername is allowed to do $request in policy tbl. $peername, if set signifies client has a cert that the xCAT CA accepted. Logs to syslog and auditlog table all user commands, see site.auditskipcmds attribute and auditnosyslog attribute. Arguments: Returns: returns 1 if policy engine allows the action, 0 if denied. Globals: none Error: none Example: if(xCAT::XCATd->validate($peername,$peerhost,$req,$peerhostorg,\@deferredmsgargs)) { . . Comments: none =cut #------------------------------------------------------------------------------ sub validate { my $class = shift; my $peername = shift; my $peerhost = shift; my $request = shift; my $peerhostorg = shift; my $deferredmsgargs = shift; my @filtered_cmds = qw( getdestiny getbladecons getipmicons getopenbmccons getcons); # now check the policy table if user can run the command my $policytable = xCAT::Table->new('policy'); unless ($policytable) { xCAT::MsgUtils->message("S", "Unable to open policy data, denying"); return 0; } my $policies = $policytable->getAllEntries; $policytable->close; my $rule; my $peerstatus = "untrusted"; # This sorts the policy table rows based on the level of the priority field in the row. # note the lower the number in the policy table the higher the priority my @sortedpolicies = sort { $a->{priority} <=> $b->{priority} } (@$policies); # check to see if peerhost is trusted foreach $rule (@sortedpolicies) { if (($rule->{name} and $rule->{name} eq $peername) && ($rule->{rule} =~ /trusted/i)) { $peerstatus = "Trusted"; last; } } my %req_noderange_info; if (defined $request->{noderange}->[0]) { my @tmpn = xCAT::NodeRange::noderange($request->{noderange}->[0]); $req_noderange_info{leftnodenum} = @tmpn; if($req_noderange_info{leftnodenum}){ $req_noderange_info{leftnodes} = \@tmpn; } } RULE: foreach $rule (@sortedpolicies) { if ($rule->{name} and $rule->{name} ne '*') { #TODO: more complex matching (lists, wildcards) next unless ($peername and $peername eq $rule->{name}); } if ($rule->{name} and $rule->{name} eq '*') { #a name is required, but can be any name whatsoever.... next unless ($peername); } if ($rule->{time} and $rule->{time} ne '*') { #TODO: time ranges } if ($rule->{host} and $rule->{host} ne '*') { #TODO: more complex matching (lists, noderanges?, wildcards) next unless ($peerhost eq $rule->{host}); } if ($rule->{commands} and $rule->{commands} ne '*') { my @commands = split(",", $rule->{commands}); my $found = 0; foreach my $cmd (@commands) { if ($request->{command}->[0] eq $cmd) { $found = 1; last; } } if ($found == 0) { # no command match next; } } if ($rule->{parameters} and $rule->{parameters} ne '*') { my $parms; if ($request->{arg}) { $parms = join(' ', @{ $request->{arg} }); } else { $parms = ""; } my $patt = $rule->{parameters}; unless ($parms =~ /$patt/) { next; } } if ($rule->{noderange} and $rule->{noderange} ne '*') { unless($req_noderange_info{leftnodenum}){ next RULE; } my $allow = 0; if ($rule->{rule} =~ /allow/i or $rule->{rule} =~ /accept/i or $rule->{rule} =~ /trusted/i) { $allow = 1; } my %rulenodes; foreach (noderange($rule->{noderange})) { $rulenodes{$_} = 1; } my $hitnum = 0; my @non_hit_nodes; foreach (@{$req_noderange_info{leftnodes}}) { if (defined($rulenodes{$_})) { ++$hitnum; }else{ push @non_hit_nodes, $_; } } if($hitnum == 0){ next RULE; }elsif($hitnum && $hitnum != $req_noderange_info{leftnodenum}){ if($allow){ $req_noderange_info{leftnodenum} = @non_hit_nodes; $req_noderange_info{leftnodes} = \@non_hit_nodes; next RULE; } } } # If we are still in, that means this rule is the first match and dictates behavior. # We are not going to log getdestiny,getbladecons,getipmicons commands, way # too many of them #print Dumper($request); if ($rule->{rule}) { my $logst; my $rc; my $status; if ($rule->{rule} =~ /allow/i or $rule->{rule} =~ /accept/i or $rule->{rule} =~ /trusted/i) { $logst = "xCAT: Allowing " . $request->{command}->[0]; $status = "Allowed"; $rc = 1; } else { $logst = "xCAT: Denying " . $request->{command}->[0]; $status = "Denied"; $rc = 0; } if (! grep { /$request->{command}->[0]/ } @filtered_cmds) { # set username authenticated to run command # if from Trusted host, use input username, else set from creds if (($request->{username}) && defined($request->{username}->[0])) { if ($peerstatus ne "Trusted") { # then set to peername $request->{username}->[0] = $peername; } } else { $request->{username}->[0] = $peername; } if ($request->{noderange} && defined($request->{noderange}->[0])) { $logst .= " to " . $request->{noderange}->[0]; } else { # no noderange maybe a nodes if ($request->{node} && defined($request->{node}->[0])) { my @reqnodes = @{ $request->{node} }; if (@reqnodes) { $logst .= " to "; foreach my $node (@reqnodes) { $logst .= "$node,"; } chop $logst; } } } # add each argument my $args = $request->{arg}; my $arglist; foreach my $argument (@$args) { $arglist .= " " . $argument; } my $saveArglist = $arglist; # If this is mkvm check for --password or -w if ($request->{command}->[0] eq "mkvm") { my $first; my $restcommand; my $passw = index ($saveArglist, '--password'); if ($passw > -1) { $passw = $passw + 11; my $first = substr($saveArglist,0,$passw). "******** "; my $restcommand = substr($saveArglist,$passw); $restcommand =~ s/^\S+\s*//; $saveArglist = "$first$restcommand"; } # now check for -w with password $passw = index ($saveArglist, '-w'); if ($passw > -1) { $passw = $passw + 3; $first = substr($saveArglist,0,$passw). "******** "; $restcommand = substr($saveArglist,$passw); $restcommand =~ s/^\S+\s*//; $saveArglist = "$first$restcommand"; } } if ($arglist) { $logst .= $saveArglist; } if ($peername) { $logst .= " for " . $request->{username}->[0] } if ($peerhost) { $logst .= " from " . $peerhost } # read site.auditskipcmds and auditnosyslog attributes, # if set skip commands else audit all cmds. # is auditnosyslog, then only write to auditlog table and not to syslog my @skipcmds = ($::XCATSITEVALS{auditskipcmds}); my $auditnosyslog = ($::XCATSITEVALS{auditnosyslog}); my $skipsyslog = 0; # default is to write all commands to auditlog and syslog if (defined($auditnosyslog)) { $skipsyslog = $auditnosyslog; # take setting from site table, 1 means no syslog } # if not "ALL" and not a command from site.auditskipcmds # and not getcredentials and not getcredentials , # put in syslog and auditlog my $skip = 0; my $all = "all"; if (defined($skipcmds[0])) { # if there are values if (grep(/$all/i, @skipcmds)) { # skip all $skip = 1; } else { if (grep(/$request->{command}->[0]/, @skipcmds)) { # skip the command $skip = 1; } # if skip clienttype clienttype:value my $client = "clienttype:"; $client .= $request->{clienttype}->[0]; if (grep(/$client/, @skipcmds)) { #skip the client $skip = 1; } } } @$deferredmsgargs = (); #should be redundant, but just in case if (($request->{command}->[0] ne "getpostscript") && ($request->{command}->[0] ne "getcredentials") && ($skip == 0)) { # put in audit Table and syslog unless site.noauditsyslog=1 my $rsp = {}; if ($skipsyslog == 0) { # write to syslog and auditlog $rsp->{syslogdata}->[0] = $logst; # put in syslog data } if ($peername) { $rsp->{userid}->[0] = $request->{username}->[0]; } if ($peerhost) { $rsp->{clientname}->[0] = $peerhost; } if (defined $request->{clienttype}) { $rsp->{clienttype}->[0] = $request->{clienttype}->[0]; } else { if (defined $request->{becomeuser}) { $rsp->{clienttype}->[0] = "webui"; } else { $rsp->{clienttype}->[0] = "other"; } } $rsp->{command}->[0] = $request->{command}->[0]; if ($request->{noderange} && defined($request->{noderange}->[0])) { $rsp->{noderange}->[0] = $request->{noderange}->[0]; } $rsp->{args}->[0] = $arglist; $rsp->{status}->[0] = $status; if ($skipsyslog == 0) { # write to syslog and auditlog @$deferredmsgargs = ("SA", $rsp); } else { # only auditlog @$deferredmsgargs = ("A", $rsp); } } else { # getpostscript or getcredentials, just syslog if (($request->{command}->[0] eq "getpostscript") || ($request->{command}->[0] eq "getcredentials")) { unless ($::XCATSITEVALS{skipvalidatelog}) { @$deferredmsgargs = ("S", $logst); } } else { #other skipped command syslog unless auditnosyslog if ($skipsyslog == 0) { # write to syslog @$deferredmsgargs = ("S", $logst); } } } } # end getbladecons,etc check return $rc; } else { #Shouldn't be possible.... xCAT::MsgUtils->message("S", "Impossible line in xcatd reached"); return 0; } } # end RULE #Reached end of policy table, reject by default. if($req_noderange_info{leftnodenum}){ my $leftnodes = join(",", @{$req_noderange_info{leftnodes}}); xCAT::MsgUtils->message("S", "Request matched no policy rule: peername=$peername, peerhost=$peerhost $request->{command}->[0] to $leftnodes"); }else{ xCAT::MsgUtils->message("S", "Request matched no policy rule: peername=$peername, peerhost=$peerhost " . $request->{command}->[0]); } return 0; } my $tokentimeout = 86400; # one day # this subroutine search the token table # 1. find the existed token entry for the user and reset the expire time # 1.1. if not find existed token, create a new one and add it to token table # 2. clean up the expired token # # this subroutine is called after the account has been authorized sub gettoken { my $class = shift; my $req = shift; my $user = $req->{gettoken}->[0]->{username}->[0]; my $tokentb = xCAT::Table->new('token'); unless ($tokentb) { return undef; } my $tokens = $tokentb->getAllEntries; foreach my $token (@{$tokens}) { #clean the expired token if ($token->{'expire'} < time()) { $tokentb->delEntries({ 'tokenid' => $token->{tokenid} }); } } # create a new token for this request my $uuid = xCAT::Utils->genUUID(); my $expiretime = time() + $tokentimeout; $tokentb->setAttribs({ tokenid => $uuid, username => $user }, { expire => $expiretime }); $tokentb->close(); return ($uuid, $expiretime); } # verify the token has correct entry in token table and expire time is not exceeded. sub verifytoken { my $class = shift; my $req = shift; my $tokenid = $req->{tokens}->[0]->{tokenid}->[0]; my $tokentb = xCAT::Table->new('token'); unless ($tokentb) { return undef; } my $token = $tokentb->getAttribs({ 'tokenid' => $tokenid }, ('username', 'expire')); if (defined($token) && defined($token->{'username'}) && defined($token->{'expire'})) { my $expiretime = time() + $tokentimeout; if ($token->{'expire'} < time()) { $tokentb->delEntries({ 'tokenid' => $token->{tokenid} }); return undef; } else { return $token->{'username'}; } } else { return undef; } } 1;