* Created tools/anvil-manage-keys to handle user requests to remove bad keys from known_hosts files for target machines that have been rebuilt or replaced.

* Added a check to Remote->call() where, when a connect attempt fails because of a changed/bad key, it is reported as such to the user/logs and an entry is recorded in the state file.
* Started adding a Striker menu function showing users a list of bad keys in known_hosts files and the ability to remove old keys.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 5 years ago
parent 37f36fe99c
commit 566ec896ca
  1. 1
      Anvil/Tools.pm
  2. 2
      Anvil/Tools/Database.pm
  3. 20
      Anvil/Tools/Remote.pm
  4. 202
      cgi-bin/striker
  5. BIN
      html/skins/alteeve/images/broken_key_icon_off.png
  6. BIN
      html/skins/alteeve/images/broken_key_icon_on.png
  7. 3
      html/skins/alteeve/images/sources.txt
  8. 3
      html/skins/alteeve/pxe.txt
  9. 40
      html/skins/alteeve/striker.html
  10. 19
      share/words.xml
  11. 268
      tools/anvil-manage-keys

@ -1061,6 +1061,7 @@ sub _set_paths
'anvil-file-details' => "/usr/sbin/anvil-file-details",
'anvil-maintenance-mode' => "/usr/sbin/anvil-maintenance-mode",
'anvil-manage-firewall' => "/usr/sbin/anvil-manage-firewall",
'anvil-manage-keys' => "/usr/sbin/anvil-manage-keys",
'anvil-manage-power' => "/usr/sbin/anvil-manage-power",
'anvil-report-memory' => "/usr/sbin/anvil-report-memory",
'anvil-update-files' => "/usr/sbin/anvil-update-files",

@ -4517,8 +4517,6 @@ sub insert_or_update_states
my $state_host_uuid = defined $parameter->{state_host_uuid} ? $parameter->{state_host_uuid} : $anvil->data->{sys}{host_uuid};
my $state_note = defined $parameter->{state_note} ? $parameter->{state_note} : "";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
uuid => $uuid,
"cache::database_handle::${uuid}" => $anvil->data->{cache}{database_handle}{$uuid},
uuid => $uuid,
file => $file,
line => $line,

@ -471,6 +471,7 @@ sub call
elsif ($connect_output =~ /IDENTIFICATION HAS CHANGED/i)
{
# Host's ID has changed, rebuilt? Find the line and file to tell the user.
my $user = getpwuid($<);
foreach my $line (split/\n/, $connect_output)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }});
@ -487,8 +488,23 @@ sub call
$message_key = "message_0149";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { i => $i, message_key => $message_key }});
# TODO: Store this in the states table and have a function that makes
# removing the offending line from the WebUI.
# If I have a database connection, record this bad entry in 'states'.
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'sys::database::connections' => $anvil->data->{sys}{database}{connections} }});
if (not $anvil->data->{sys}{database}{connections})
{
# Try to connect
$anvil->Database->connect();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"});
}
if ($anvil->data->{sys}{database}{connections})
{
my ($state_uuid) = $anvil->Database->insert_or_update_states({
debug => 2,
state_name => "host_key_changed::".$target."::".$user,
state_note => "file=".$bad_file.",line=".$bad_line,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { state_uuid => $state_uuid }});
}
}
elsif ($connect_output =~ /Host key verification failed/i)
{

@ -388,6 +388,10 @@ sub process_striker_menu
{
process_power($anvil, "poweroff");
}
elsif ($anvil->data->{cgi}{task}{value} eq "keys")
{
process_keys($anvil, "poweroff");
}
else
{
# What we show for the reboot icon and text depends on if a reboot is pending.
@ -433,6 +437,23 @@ sub process_striker_menu
install_target_subtask => $install_target_subtask,
}});
# Are there any bad keys?
my $query = "SELECT state_uuid, state_note FROM states WHERE state_name LIKE 'host_key_changed::%';";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
results => $results,
count => $count,
}});
my $broken_key_icon = $count ? "broken_key_icon_on.png" : "broken_key_icon_off.png";
my $broken_key_message = $count ? "#!string!striker_0132!#" : "#!string!striker_0131!#";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
broken_key_icon => $broken_key_icon,
broken_key_message => $broken_key_message,
}});
# The 'back' goes home
$anvil->data->{form}{back_link} = "?";
$anvil->data->{form}{refresh_link} = "?striker=true";
@ -442,6 +463,8 @@ sub process_striker_menu
install_target_icon => $install_target_icon,
install_target_title => $install_target_title,
install_target_subtask => $install_target_subtask,
broken_key_icon => $broken_key_icon,
broken_key_message => $broken_key_message,
}});
}
@ -469,6 +492,104 @@ sub process_jobs_menu
return(0);
}
# This handles removing old keys.
sub process_keys
{
my ($anvil) = @_;
### TODO: Left off here... Create a check-box list of state_uuid(s) the user selected, if any, and store them as a comma-separated list.
### NOTE: This doesn't update Striker (the Alteeve) stack yet, just the base OK.
$anvil->data->{cgi}{confirm}{value} = "" if not defined $anvil->data->{cgi}{confirm}{value};
if ($anvil->data->{cgi}{confirm}{value})
{
# Record the job!
my $job_data = "";
die;
my ($job_uuid) = $anvil->Database->insert_or_update_jobs({
file => $THIS_FILE,
line => __LINE__,
job_command => $anvil->data->{path}{exe}{'anvil-manage-keys'},
job_data => "",
job_name => "manage::keys",
job_title => "job_0056",
job_description => "job_0057",
job_progress => 0,
job_host_uuid => $anvil->data->{sys}{host_uuid},
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { job_uuid => $job_uuid }});
# We don't need to store anything as hidden variables, we'll read it back from the database
# later.
$anvil->data->{form}{body} = $anvil->Template->get({file => "striker.html", name => "job recorded", variables => {
title_id => "",
message_id => "",
reload_url => "/cgi-bin/".$THIS_FILE,
title => "#!string!job_0056!#",
description => "#!string!job_0057!#",
}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "form::body" => $anvil->data->{form}{body} }});
}
else
{
# Get a list of bad keys we know about and ask the user which they want to remove
my $query = "
SELECT
state_uuid,
state_host_uuid,
state_name,
state_note
FROM
states
AND
state_name LIKE 'host_key_changed::%'
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
results => $results,
count => $count,
}});
if (not $count)
{
# No bad keys found on this host.
$anvil->data->{form}{body} = $anvil->Template->get({file => "striker.html", name => "no-bad-keys"});
}
else
{
# Build a list of check-boxes / bad keys.
my $bad_key_list = "";
foreach my $row (@{$results})
{
my $state_uuid = $row->[0];
my $state_host_uuid = $row->[1];
my $state_name = $row->[2];
my $state_note = $row->[3];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
state_uuid => $state_uuid,
state_host_uuid => $state_host_uuid,
state_name => $state_name,
state_note => $state_note,
}});
}
}
die;
#Show the screen the confirm the addition.
$anvil->data->{form}{body} = $anvil->Template->get({file => "striker.html", name => "confirm-action", variables => {
title => "#!string!job_0056!#",
message => "#!string!!#",
hidden_fields => "",
}});
}
return(0);
}
# This handles files.
sub process_file_menu
{
@ -667,8 +788,59 @@ sub process_prep_host_page
### NOTE: Left off here. Need to pick up the message from a bad/changed fingerprint when it's the cause of a failed login. Create a button to remove the bad key.
if (not $connected)
{
# Is it because the target's key is bad or has changed?
my $query = "SELECT state_uuid, state_note FROM states WHERE state_name LIKE ".$anvil->Database->quote("host_key_changed::".$host_ip_address."::%").";";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
results => $results,
count => $count,
}});
if ($count)
{
# Yup, key has changed.
my $state_uuid = $results->[0]->[0];
my $state_note = $results->[0]->[1];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
state_uuid => $state_uuid,
state_note => $state_note,
}});
my $bad_file = "";
my $bad_line = "";
foreach my $pair (split/,/, $state_note)
{
my ($variable, $value) = ($pair =~ /^(.*?)=(.*)$/);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
pair => $pair,
variable => $variable,
value => $value,
}});
if ($variable eq "file")
{
$bad_file = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bad_file => $bad_file }});
}
if ($variable eq "line")
{
$bad_line = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bad_line => $bad_line }});
}
}
my $error_message = $anvil->Words->string({key => "striker_warning_0013", variables => {
file => $bad_file,
line => $bad_line,
}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { error_message => $error_message }});
$anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $error_message }});
}
else
{
# Nope, some other issue
$anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $anvil->Words->string({key => "striker_warning_0012"}) }});
}
}
elsif (not $target_host_uuid)
{
$anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $anvil->Words->string({key => "striker_warning_0005", variables => { uuid => $target_host_uuid }}) }});
@ -773,7 +945,6 @@ sub process_power
my ($anvil, $task) = @_;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { task => $task }});
### NOTE: This doesn't update Striker (the Alteeve) stack yet, just the base OK.
$anvil->data->{cgi}{confirm}{value} = "" if not defined $anvil->data->{cgi}{confirm}{value};
if ($anvil->data->{cgi}{confirm}{value})
{
@ -836,7 +1007,6 @@ sub process_update
{
my ($anvil) = @_;
### NOTE: This doesn't update Striker (the Alteeve) stack yet, just the base OK.
$anvil->data->{cgi}{confirm}{value} = "" if not defined $anvil->data->{cgi}{confirm}{value};
if ($anvil->data->{cgi}{confirm}{value})
{
@ -1474,17 +1644,16 @@ sub check_availability
{
my ($anvil) = @_;
my $debug = 3;
my $available = 1;
# Check maintenance mode.
my $maintenance_mode = $anvil->System->maintenance_mode({debug => $debug});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { maintenance_mode => $maintenance_mode }});
my $maintenance_mode = $anvil->System->maintenance_mode({debug => 3});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { maintenance_mode => $maintenance_mode }});
if ($maintenance_mode)
{
$available = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { available => $available }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { available => $available }});
# If we have any running or recently finished jobs, we'll desplay them below.
$anvil->data->{say}{maintenance} = $anvil->Template->get({file => "striker.html", name => "striker-offline", variables => {
@ -1514,11 +1683,11 @@ AND
AND
job_host_uuid = ".$anvil->Database->quote($anvil->Get->host_uuid)."
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
results => $results,
count => $count,
}});
@ -1530,7 +1699,7 @@ AND
my $unixtime = $results->[0]->[2];
my $seconds_ago = $anvil->Convert->add_commas({number => (time - $unixtime)});
$available = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
available => $available,
percent => $percent,
seconds_ago => $seconds_ago,
@ -1544,11 +1713,11 @@ AND
title => "#!string!striker_0046!#",
description => $anvil->Words->string({key => "striker_0047", variables => { percent => $percent, timestamp => $timestamp, seconds_ago => $seconds_ago }}),
}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'say::maintenance' => $anvil->data->{say}{maintenance} }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 'say::maintenance' => $anvil->data->{say}{maintenance} }});
}
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { available => $available }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { available => $available }});
return($available);
}
@ -3007,8 +3176,7 @@ z = Device Sequence
sub generate_ip
{
my ($anvil, $network, $network_sequence, $device_sequence) = @_;
my $debug = 3;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
network => $network,
network_sequence => $network_sequence,
device_sequence => $device_sequence,
@ -3020,7 +3188,7 @@ sub generate_ip
# The subnet's second octet will be '+X' where 'X' is the sequence.
my $default_ip = $anvil->data->{defaults}{network}{$network}{subnet};
my $default_netmark = $anvil->data->{defaults}{network}{$network}{netmask};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
default_ip => $default_ip,
default_netmark => $default_netmark,
}});
@ -3029,7 +3197,7 @@ sub generate_ip
{
# Valid values.
my ($ip_octet1, $ip_octet2, $ip_octet3, $ip_octet4) = (split/\./, $default_ip);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
ip_octet1 => $ip_octet1,
ip_octet2 => $ip_octet2,
ip_octet3 => $ip_octet3,
@ -3046,7 +3214,7 @@ sub generate_ip
$ip_octet3 = $anvil->data->{defaults}{network}{$network}{striker_octet3};
$ip_octet4 = $device_sequence;
$ip = $ip_octet1.".".$ip_octet2.".".$ip_octet3.".".$ip_octet4;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
"s1:ip_octet2" => $ip_octet2,
"s2:ip_octet3" => $ip_octet3,
"s3:ip_octet4" => $ip_octet4,
@ -3060,6 +3228,6 @@ sub generate_ip
$ip = "#!error!#";
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ip => $ip }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { ip => $ip }});
return($ip);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

@ -55,3 +55,6 @@ configure by iconesia from the Noun Project (https://thenounproject.com/term/con
Folder by Mint Shirt from the Noun Project (https://thenounproject.com/term/folder/402164/)
- files_icon.png
Broken Key by I Putu Kharismayadi from the Noun Project (https://thenounproject.com/term/folder/1481931/)
- broken_key.png

@ -193,7 +193,10 @@ chmod 755 /mnt/sysimage/usr/sbin/anvil-update-issue
# Add this to crontab.
cat << EOF > /mnt/sysimage/var/spool/cron/root
MAILTO=""
# First one keeps the list updated, the second one to make sure the list is
# there before the first login page is shown.
* * * * * /usr/sbin/anvil-update-issue >> /var/log/anvil.cron 2>&1
@reboot /usr/sbin/anvil-update-issue >> /var/log/anvil.cron 2>&1
EOF
chown 0:0 /mnt/sysimage/var/spool/cron/root
chmod 0600 /mnt/sysimage/var/spool/cron/root

@ -161,6 +161,33 @@
</table>
<!-- end jobs -->
<!-- start no-bad-keys -->
<table align="center">
<div id="confirm-update">
<tr>
<td class="menu_title" colspan="2">
#!string!striker_0133!#
</td>
</tr>
<tr>
<td class="menu_details" colspan="2">
#!string!striker_0134!#
</td>
</tr>
<tr>
<td colspan="2">
&nbsp;
</td>
</tr>
<tr>
<td class="button_cell" style="text-align: left;">
<a href="/cgi-bin/striker?striker=true" class="button">#!string!striker_0098!#</a>
</td>
</tr>
</div>
</table>
<!-- end no-bad-keys -->
<!-- start reconfig-done -->
<table>
<tr>
@ -373,6 +400,19 @@
#!string!striker_0110!#
</td>
</tr>
<tr>
<td rowspan="2" class="icon_button">
<a href="?striker=true&task=keys"><img src="#!data!skin::url!#/images/#!variable!broken_key_icon!#" class="top_icon" ></a>
</td>
<td class="menu_title">
<a href="?striker=true&task=keys">#!string!striker_0133!#</a>
</td>
</tr>
<tr>
<td class="menu_details">
#!variable!broken_key_message!#
</td>
</tr>
<tr>
<td rowspan="2" class="icon_button">
<a href="?striker=true&task=reboot"><img src="#!data!skin::url!#/images/#!variable!reboot_icon!#" class="top_icon" ></a>

@ -932,6 +932,10 @@ Here we will inject 't_0006', which injects 't_0001' which has a variable: [#!st
<key name="striker_0128">Initialize</key>
<key name="striker_0129">The target will now be initialized. How long this takes will depend on how fast files can be downloaded and, when needed, how long it takes to register with Red Hat and add the needed repositories.</key>
<key name="striker_0130">Configure the network on a node or DR host.</key>
<key name="striker_0131">This option will allow old machine keys to be removed. This is not currently needed.</key>
<key name="striker_0132">There are one or more broken keys, blocking access to target machines. If a target has been rebuilt, you can clear the old keys here.</key>
<key name="striker_0133">Manage Changed Keys</key>
<key name="striker_0134">There are no known bad keys at this time.</key>
<!-- These are generally units and appended to numbers -->
<key name="suffix_0001">#!variable!number!#/sec</key>
@ -955,7 +959,7 @@ Here we will inject 't_0006', which injects 't_0001' which has a variable: [#!st
<key name="job_0006">This system will be rebooted momentarily. It will not respond until it has booted back up.</key>
<key name="job_0007">Poweroff Striker</key>
<key name="job_0008">This system will be powered off momentarily. It will not respond until it has turned back on.</key>
<key name="job_0009">Reboot..</key>
<key name="job_0009">Reboot...</key>
<key name="job_0010">Powering off...</key>
<key name="job_0011">Add a Striker Peer</key>
<key name="job_0012">The Striker peer will now be added to the local configuration.</key>
@ -998,6 +1002,16 @@ Failure! The return code: [#!variable!return_code!#] was received ('0' was expec
<key name="job_0045">Success!</key>
<key name="job_0046">Adding our database connection information to the target's anvil.conf file!</key>
<key name="job_0047">Finished! The target should be ready for initial configuration shortly. If it isn't, please check that the 'anvil-daemon' daemon is running.</key>
<key name="job_0048">Removing bad machine keys.</key>
<key name="job_0049">Removing line: [#!variable!line!#] from: [#!variable!file!#] for the target machine: [#!variable!target!#].</key>
<key name="job_0050">[ Error ] - The known hosts file: [#!variable!file!#] was not found. Skipping it.</key>
<key name="job_0051">Finished.</key>
<key name="job_0052">[ Error ] - There was a problem reading the known hosts file: [#!variable!file!#]. Skipping it.</key>
<key name="job_0053">Found the offending line, removing it.</key>
<key name="job_0054">[ Error ] - The line number: [#!variable!line!#] in: [#!variable!file!#] does not appear to be for the target: [#!variable!target!#]. Has the file already been updated? Skipping it.</key>
<key name="job_0055">Rewriing: [#!variable!file!#].</key>
<key name="job_0056">Manage Keys</key>
<key name="job_0057">The bad keys will be removed from the specified files.</key>
<!-- Warnings -->
<key name="striker_warning_0001">The IP address will change. You will need to reconnect after applying these changes.</key>
@ -1012,6 +1026,7 @@ Failure! The return code: [#!variable!return_code!#] was received ('0' was expec
<key name="striker_warning_0010">The IP address is not a valid IPv4 address</key>
<key name="striker_warning_0011">The SSH port is not a valid (usually it is 22, but it has to be between 1 ~ 65536)</key>
<key name="striker_warning_0012">Failed to log into the host. Is the IP or root user's password right?</key>
<key name="striker_warning_0013">The target's host key has changed. If the target has been rebuilt, or the target IP reused, the old key will need to be removed. Problem file: [#!variable!file!#:#!variable!line!#].</key>
<!-- Errors -->
<key name="error_0001">There are not enough network interfaces on this machine. You have: [#!variable!interface_count!#] interface(s), and you need at least: [#!variable!required_interfaces_for_single!#] interfaces to connect to the requested networks (one for Back-Channel and one for each Internet-Facing network).</key>
@ -1105,6 +1120,8 @@ Failed to generate an RSA public key for the user: [#!variable!user!#]. The outp
<key name="error_0074">The initialization target: [#!variable!target!#] is not accessible. Will keep trying...</key>
<key name="error_0075">There are no databases available. Will check periodically, waiting until one becomes available.</key>
<key name="error_0076">There was a problem adding out database to the target's anvil.conf file.</key>
<key name="error_0077">Unable to connect to the database, unable to read the details of the key to remove.</key>
<key name="error_0078">Did not find any offending keys on this host, exiting.</key>
<!-- These are units, words and so on used when displaying information. -->
<key name="unit_0001">Yes</key>

@ -0,0 +1,268 @@
#!/usr/bin/perl
#
# This removes a bad key from a
#
# This program is setuid 'admin' and calls a (new) peer to read its hostname and system UUID. It takes the
# target's password in via a file.
#
# Exit codes;
# 0 = Normal exit.
# 1 = No database connection.
# 2 = No offending keys found.
# 3 =
#
use strict;
use warnings;
use Anvil::Tools;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
# Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete.
$| = 1;
my $anvil = Anvil::Tools->new();
$anvil->Log->level({set => 2});
$anvil->Log->secure({set => 1});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }});
# Read switches (target ([user@]host[:port]) and the file with the target's password. If the password is
# passed directly, it will be used. Otherwise, the password will be read from the database.
$anvil->Get->switches;
$anvil->Database->connect();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"});
if (not $anvil->data->{sys}{database}{connections})
{
# No databases, update the job, sleep for a bit and then exit. The daemon will pick it up and try
# again after we exit.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0077"});
sleep 10;
$anvil->nice_exit({exit_code => 1});
}
### TODO: Store the state_uuid(s) of the key(s) to remove.
# If we don't have a state_uuid, pick it up from the job_data
# Read in the details and make sure the bad the bad key is on our system.
my $query = "SELECT
state_uuid,
state_name,
state_note
FROM
states
WHERE
state_host_uuid = ".$anvil->Database->quote($anvil->data->{sys}{host_uuid})."
AND
state_name LIKE 'host_key_changed::%'
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
results => $results,
count => $count,
}});
if (not $count)
{
# No bad keys found on this host.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0078"});
sleep 10;
$anvil->nice_exit({exit_code => 2});
}
my $progress = 0;
update_progress($anvil, 0, "clear");
$progress += 5;
update_progress($anvil, $progress, "job_0048");
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0048"});
foreach my $row (@{$results})
{
my $state_uuid = $row->[0];
my $state_name = $row->[1];
my $state_note = $row->[2];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
state_uuid => $state_uuid,
state_name => $state_name,
state_note => $state_note,
}});
# Pull out the details.
my $bad_file = "";
my $bad_line = "";
foreach my $pair (split/,/, $state_note)
{
my ($variable, $value) = ($pair =~ /^(.*?)=(.*)$/);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
pair => $pair,
variable => $variable,
value => $value,
}});
if ($variable eq "file")
{
$bad_file = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bad_file => $bad_file }});
}
if ($variable eq "line")
{
$bad_line = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bad_line => $bad_line }});
}
}
my ($target, $user) = ($state_name =~ /host_key_changed::(.*)::(.*)$/);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
target => $target,
user => $user,
bad_file => $bad_file,
bad_line => $bad_line,
}});
$progress += 5; $progress = 95 if $progress > 95;
update_progress($anvil, $progress, "job_0049,!!line!:".$bad_line.",!!file!".$bad_file."!!,!!target!".$target."!!");
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0049", variables => {
line => $bad_line,
file => $bad_file,
target => $target,
}});
# Read in the file, if it exists.
if (not -e $bad_file)
{
$progress += 10; $progress = 95 if $progress > 95;
update_progress($anvil, $progress, "job_0050,!!file!".$bad_file."!!");
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0050", variables => { file => $bad_file }});
# Remove this job and go on to the next bad key (if any).
delete_state($anvil, $state_uuid);
next;
}
# Read in the file
my ($old_body) = $anvil->Storage->read_file({file => $bad_file});
if ($old_body eq "!!error!!")
{
# Failed to read the file
$progress += 10; $progress = 95 if $progress > 95;
update_progress($anvil, $progress, "job_0052,!!file!".$bad_file."!!");
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0052", variables => { file => $bad_file }});
# Remove this job and go on to the next bad key (if any).
delete_state($anvil, $state_uuid);
next;
}
# Find our key
my $line_number = 0;
my $new_body = "";
my $update = 0;
foreach my $line (split/\n/, $old_body)
{
$line_number++;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
's1:line_number' => $line_number,
's2:bad_line' => $bad_line,
's3:line' => $line,
}});
if ($line_number eq $bad_line)
{
# Verify that this is, indeed, the right line.
if ($line =~ /^$target /)
{
# Found it!
$progress += 5; $progress = 95 if $progress > 95;
update_progress($anvil, $progress, "job_0053");
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0053"});
$update = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { update => $update }});
last;
}
else
{
# Line found, but not for the target.
$progress += 10; $progress = 95 if $progress > 95;
update_progress($anvil, $progress, "job_0054,!!line!".$bad_line."!!,!!file!".$bad_file."!!,!!target!".$target."!!");
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0054", variables => {
line => $bad_line,
file => $bad_file,
target => $target,
}});
# Remove this job and go on to the next bad key (if any).
delete_state($anvil, $state_uuid);
last;
}
}
else
{
$new_body .= $line."\n";
}
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
's1:old_body' => $old_body',
's2:new_body' => $new_body,
's3:update' => $update,
}});
if ($update)
{
# Write the file out.
$progress += 5; $progress = 95 if $progress > 95;
update_progress($anvil, $progress, "job_0055,!!file!".$bad_file."!!");
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0055", variables => { file => $bad_file }});
}
}
# Done.
update_progress($anvil, 100, "job_0051");
$anvil->nice_exit({code => 0});
#############################################################################################################
# Functions #
#############################################################################################################
# This deletes a state entry.
sub delete_state
{
my ($anvil, $state_uuid) = @_;
# Delete it so long as we have a UUID.
if ($state_uuid)
{
my $query = "DELETE FROM states WHERE state_uuid = ".$anvil->Database->quote($state_uuid).";";
$anvil->Database->write({debug => 2, query => $merged, source => $THIS_FILE, line => __LINE__});
}
return(0);
}
# This updates the progress if we were called with a job UUID.
sub update_progress
{
my ($anvil, $progress, $message) = @_;
# Log the progress percentage.
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
progress => $progress,
message => $message,
"jobs::job_uuid" => $anvil->data->{jobs}{job_uuid},
}});
if ($anvil->data->{jobs}{job_uuid})
{
$anvil->Job->update_progress({
debug => 3,
progress => $progress,
message => $message,
job_uuid => $anvil->data->{jobs}{job_uuid},
});
}
return(0);
}
Loading…
Cancel
Save