mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-04 20:17:55 +00:00
fix(xcatd): classify secret responses by the shared secret set
The commands.log response classifier used a "passw" text match on the request arguments. A secret whose name has no such text passed the check, so a read of an authentication key, a privacy key or the snmpc site value logged its bare value in the response. A command that expands an argument also passed the check: nodels with a table name returns every column of the table, and lsdef returns attributes that the request never names. The daemon also ran redact_password over the whole connection log on each request, so the redactor split at the first request of the connection and the change signal swept the text of earlier requests and responses. Add secret_in_request. The routine reports a request that names a secret attribute, selects a secret site key, or dumps a table that owns a secret column through tabdump or nodels, from the same secret set that the argument redaction uses. The response classifier calls it, so the response of such a request logs as redacted. Add secret_in_response. The routine reports response text that holds "passw" or a secret attribute name in assignment or column form. The response finalizer calls it in place of the bare text match, so an expanded listing that carries an authentication key or a product key logs as redacted even when the request never names it. The lsvm response is the directory entry, whose passwords are positional, so the classifier marks the command itself. Build each request segment alone, redact the segment, and then append it to the connection log. The redactor now always sees the current command, and the change signal covers only the current request.
This commit is contained in:
@@ -532,6 +532,35 @@ my %secret_command_patterns = (
|
||||
my %secret_command_nocase = map { $_ => 1 } qw(mkvm);
|
||||
my %secret_site_keys = map { $_ => 1 } qw(snmpc);
|
||||
|
||||
my %secret_table = map { (split /\./)[0] => 1 } grep { /\./ } @secret_attributes;
|
||||
$secret_table{site} = 1;
|
||||
|
||||
my $secret_response_pattern = do {
|
||||
my $names = join '|', map { quotemeta } @secret_attributes;
|
||||
qr/(?:^|[^\w.])(?:$names)\s*[=:]/;
|
||||
};
|
||||
|
||||
sub secret_in_response {
|
||||
my ($class, $text) = @_;
|
||||
return 0 unless defined $text;
|
||||
return 1 if $text =~ /passw/i;
|
||||
return 1 if $text =~ $secret_response_pattern;
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub secret_in_request {
|
||||
my ($class, $command, $args) = @_;
|
||||
foreach my $arg (@{ $args || [] }) {
|
||||
next unless defined $arg;
|
||||
return 1 if defined $command and ($command eq 'tabdump' or $command eq 'nodels') and $secret_table{$arg};
|
||||
return 1 if $arg =~ /^([\w.]+)/ and $secret_attribute{$1};
|
||||
foreach my $selector (split /,/, $arg) {
|
||||
return 1 if $selector =~ /^(?:site\.)?key=([\w.]+)$/ and $secret_site_keys{$1};
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub redact_password_arg {
|
||||
my ($class, $arg) = @_;
|
||||
return $arg unless defined $arg;
|
||||
|
||||
+13
-12
@@ -2864,13 +2864,13 @@ sub service_connection {
|
||||
my $strhour = ($hour > 9 ? $hour : "0" . $hour);
|
||||
my $strmin = ($min > 9 ? $min : "0" . $min);
|
||||
my $strsec = ($sec > 9 ? $sec : "0" . $sec);
|
||||
$cmdlog_alllog .= "[Date] $year-$strmon-$strmday $strhour:$strmin:$strsec\n";
|
||||
my $cmdlog_request = "[Date] $year-$strmon-$strmday $strhour:$strmin:$strsec\n";
|
||||
|
||||
$cmdlog_alllog .= "[ClientType] " . $req->{clienttype}->[0] . " \n";
|
||||
$cmdlog_alllog .= "[Request] " . $req->{command}->[0] . " ";
|
||||
$cmdlog_request .= "[ClientType] " . $req->{clienttype}->[0] . " \n";
|
||||
$cmdlog_request .= "[Request] " . $req->{command}->[0] . " ";
|
||||
if (exists($req->{noderange}) && defined($req->{noderange}->[0])) {
|
||||
my $tmpstr = join(",", @{$req->{noderange}});
|
||||
$cmdlog_alllog .= "$tmpstr ";
|
||||
$cmdlog_request .= "$tmpstr ";
|
||||
}
|
||||
|
||||
my $cmdlog_req_redacted = 0;
|
||||
@@ -2881,17 +2881,17 @@ sub service_connection {
|
||||
if ($arg =~ /[^A-Za-z0-9.-]/) {
|
||||
my $tmparg = $arg;
|
||||
$tmparg =~ s/'/'\\''/g;
|
||||
$cmdlog_alllog .= "'" . $tmparg . "' ";
|
||||
$cmdlog_request .= "'" . $tmparg . "' ";
|
||||
} else {
|
||||
$cmdlog_alllog .= $arg . " ";
|
||||
$cmdlog_request .= $arg . " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
# Replace passwords with 'x'
|
||||
my $cmdlog_before_redact = $cmdlog_alllog;
|
||||
$cmdlog_alllog = xCAT::xcatd->redact_password($cmdlog_alllog);
|
||||
$cmdlog_req_redacted = 1 if $cmdlog_alllog ne $cmdlog_before_redact;
|
||||
$cmdlog_alllog .= "\n[Response]\n";
|
||||
my $cmdlog_before_redact = $cmdlog_request;
|
||||
$cmdlog_request = xCAT::xcatd->redact_password($cmdlog_request);
|
||||
$cmdlog_req_redacted = 1 if $cmdlog_request ne $cmdlog_before_redact;
|
||||
$cmdlog_alllog .= $cmdlog_request . "\n[Response]\n";
|
||||
$cmdlog_response_sensitive = cmdlog_response_is_sensitive($req, $cmdlog_req_redacted);
|
||||
|
||||
# ----used for command log end----------
|
||||
@@ -3630,14 +3630,15 @@ sub cmdlog_collectlog() {
|
||||
|
||||
sub cmdlog_response_is_sensitive {
|
||||
my ($req, $req_redacted) = @_;
|
||||
return 1 if defined $req->{command}->[0] and $req->{command}->[0] eq 'getcredentials';
|
||||
return 1 if defined $req->{command}->[0] and ($req->{command}->[0] eq 'getcredentials' or $req->{command}->[0] eq 'lsvm');
|
||||
return 1 if join(' ', @{ $req->{arg} || [] }) =~ /passw/i;
|
||||
return 1 if xCAT::xcatd->secret_in_request($req->{command}->[0], $req->{arg});
|
||||
return 1 if $req_redacted;
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub cmdlog_finalize_response {
|
||||
if ($cmdlog_response_sensitive or $cmdlog_response_buffer =~ /passw/i) {
|
||||
if ($cmdlog_response_sensitive or xCAT::xcatd->secret_in_response($cmdlog_response_buffer)) {
|
||||
$cmdlog_response_buffer = "*REDACTED*\n";
|
||||
}
|
||||
$cmdlog_alllog .= $cmdlog_response_buffer;
|
||||
|
||||
Reference in New Issue
Block a user