From 566ec896ca5f45de02c87a914708040ba445eeeb Mon Sep 17 00:00:00 2001 From: Digimer Date: Thu, 26 Sep 2019 01:06:16 -0400 Subject: [PATCH] * 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 --- Anvil/Tools.pm | 1 + Anvil/Tools/Database.pm | 2 - Anvil/Tools/Remote.pm | 20 +- cgi-bin/striker | 204 +++++++++++-- .../alteeve/images/broken_key_icon_off.png | Bin 0 -> 21018 bytes .../alteeve/images/broken_key_icon_on.png | Bin 0 -> 21575 bytes html/skins/alteeve/images/sources.txt | 3 + html/skins/alteeve/pxe.txt | 3 + html/skins/alteeve/striker.html | 40 +++ share/words.xml | 19 +- tools/anvil-manage-keys | 268 ++++++++++++++++++ 11 files changed, 537 insertions(+), 23 deletions(-) create mode 100644 html/skins/alteeve/images/broken_key_icon_off.png create mode 100644 html/skins/alteeve/images/broken_key_icon_on.png create mode 100755 tools/anvil-manage-keys diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 75ddd2e5..e2791e9f 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -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", diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm index 6ecf57f9..072f4983 100644 --- a/Anvil/Tools/Database.pm +++ b/Anvil/Tools/Database.pm @@ -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, diff --git a/Anvil/Tools/Remote.pm b/Anvil/Tools/Remote.pm index dba3542f..13791569 100644 --- a/Anvil/Tools/Remote.pm +++ b/Anvil/Tools/Remote.pm @@ -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) { diff --git a/cgi-bin/striker b/cgi-bin/striker index 7424cc00..b6e83549 100755 --- a/cgi-bin/striker +++ b/cgi-bin/striker @@ -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,7 +788,58 @@ 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) { - $anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $anvil->Words->string({key => "striker_warning_0012"}) }}); + # 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) { @@ -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); } diff --git a/html/skins/alteeve/images/broken_key_icon_off.png b/html/skins/alteeve/images/broken_key_icon_off.png new file mode 100644 index 0000000000000000000000000000000000000000..789c439b6a3686d5d3d467f6e2f855639987f6df GIT binary patch literal 21018 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 N;b41+RA%iP|1TspEwcas literal 0 HcmV?d00001 diff --git a/html/skins/alteeve/images/broken_key_icon_on.png b/html/skins/alteeve/images/broken_key_icon_on.png new file mode 100644 index 0000000000000000000000000000000000000000..ff68754b862657192ef9459932c285d98026eeb6 GIT binary patch literal 21575 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 literal 0 HcmV?d00001 diff --git a/html/skins/alteeve/images/sources.txt b/html/skins/alteeve/images/sources.txt index 293da6c5..3cc078c4 100644 --- a/html/skins/alteeve/images/sources.txt +++ b/html/skins/alteeve/images/sources.txt @@ -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 diff --git a/html/skins/alteeve/pxe.txt b/html/skins/alteeve/pxe.txt index e528a1a8..2c8044c2 100644 --- a/html/skins/alteeve/pxe.txt +++ b/html/skins/alteeve/pxe.txt @@ -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 diff --git a/html/skins/alteeve/striker.html b/html/skins/alteeve/striker.html index 664a006b..f192bbc9 100644 --- a/html/skins/alteeve/striker.html +++ b/html/skins/alteeve/striker.html @@ -161,6 +161,33 @@ + + +
+
+ + + + + + + + + + + + +
+   +
+ #!string!striker_0098!# +
+ + @@ -373,6 +400,19 @@ #!string!striker_0110!# + + + + + + +
+ +
diff --git a/share/words.xml b/share/words.xml index 3ffbf7fd..cc6d6626 100644 --- a/share/words.xml +++ b/share/words.xml @@ -932,6 +932,10 @@ Here we will inject 't_0006', which injects 't_0001' which has a variable: [#!st Initialize 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. Configure the network on a node or DR host. + This option will allow old machine keys to be removed. This is not currently needed. + 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. + Manage Changed Keys + There are no known bad keys at this time. #!variable!number!#/sec @@ -955,7 +959,7 @@ Here we will inject 't_0006', which injects 't_0001' which has a variable: [#!st This system will be rebooted momentarily. It will not respond until it has booted back up. Poweroff Striker This system will be powered off momentarily. It will not respond until it has turned back on. - Reboot.. + Reboot... Powering off... Add a Striker Peer The Striker peer will now be added to the local configuration. @@ -998,6 +1002,16 @@ Failure! The return code: [#!variable!return_code!#] was received ('0' was expec Success! Adding our database connection information to the target's anvil.conf file! Finished! The target should be ready for initial configuration shortly. If it isn't, please check that the 'anvil-daemon' daemon is running. + Removing bad machine keys. + Removing line: [#!variable!line!#] from: [#!variable!file!#] for the target machine: [#!variable!target!#]. + [ Error ] - The known hosts file: [#!variable!file!#] was not found. Skipping it. + Finished. + [ Error ] - There was a problem reading the known hosts file: [#!variable!file!#]. Skipping it. + Found the offending line, removing it. + [ 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. + Rewriing: [#!variable!file!#]. + Manage Keys + The bad keys will be removed from the specified files. The IP address will change. You will need to reconnect after applying these changes. @@ -1012,6 +1026,7 @@ Failure! The return code: [#!variable!return_code!#] was received ('0' was expec The IP address is not a valid IPv4 address The SSH port is not a valid (usually it is 22, but it has to be between 1 ~ 65536) Failed to log into the host. Is the IP or root user's password right? + 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!#]. 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). @@ -1105,6 +1120,8 @@ Failed to generate an RSA public key for the user: [#!variable!user!#]. The outp The initialization target: [#!variable!target!#] is not accessible. Will keep trying... There are no databases available. Will check periodically, waiting until one becomes available. There was a problem adding out database to the target's anvil.conf file. + Unable to connect to the database, unable to read the details of the key to remove. + Did not find any offending keys on this host, exiting. Yes diff --git a/tools/anvil-manage-keys b/tools/anvil-manage-keys new file mode 100755 index 00000000..46d7af6f --- /dev/null +++ b/tools/anvil-manage-keys @@ -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); +}