diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm
index a8ef4275a..7333121da 100644
--- a/Anvil/Tools.pm
+++ b/Anvil/Tools.pm
@@ -1318,6 +1318,7 @@ sub _set_paths
man => "/usr/bin/man",
md5sum => "/usr/bin/md5sum",
'mkdir' => "/usr/bin/mkdir",
+ 'mktemp' => "/usr/bin/mktemp",
modifyrepo_c => "/usr/bin/modifyrepo_c",
modprobe => "/usr/sbin/modprobe",
mv => "/usr/bin/mv",
@@ -1367,6 +1368,7 @@ sub _set_paths
snmpset => "/usr/bin/snmpset",
'sort' => "/usr/bin/sort",
ss => "/usr/sbin/ss",
+ 'ssh-copy-id' => "/usr/bin/ssh-copy-id",
'ssh-keygen' => "/usr/bin/ssh-keygen",
'ssh-keyscan' => "/usr/bin/ssh-keyscan",
'stat' => "/usr/bin/stat",
diff --git a/Anvil/Tools/Remote.pm b/Anvil/Tools/Remote.pm
index cde272962..fe38f8acf 100644
--- a/Anvil/Tools/Remote.pm
+++ b/Anvil/Tools/Remote.pm
@@ -1139,7 +1139,7 @@ sub test_access
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
- my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
+ my $debug = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Remote->test_access()" }});
my $close = defined $parameter->{'close'} ? $parameter->{'close'} : 1;
@@ -1319,106 +1319,65 @@ sub test_access
}});
return(0);
}
-
- # Read the target's authorized_keys file.
- my $target_authorized_keys_file = "/root/.ssh/authorized_keys";
- if ($user ne "root")
- {
- $target_authorized_keys_file = "/home/".$user."/.ssh/authorized_keys";
- }
- $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target_authorized_keys_file => $target_authorized_keys_file }});
-
- my $old_authorized_keys_body = $anvil->Storage->read_file({
- debug => $debug,
- file => $target_authorized_keys_file,
- force_read => 1,
- port => $port,
- password => $this_password,
- remote_user => $user,
- target => $target,
+
+ my $wrapper_script = $anvil->System->create_ssh_copy_id_wrapper({
+ debug => $debug,
+ target => $target,
+ password => $this_password,
});
- $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { old_authorized_keys_body => $old_authorized_keys_body }});
- if ($old_authorized_keys_body eq "!!error!!")
- {
- # Failed to read.
- $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0176", variables => {
- target => $target,
- password => $anvil->Log->is_secure($this_password),
- file => $public_key_file,
- }});
- return(0);
- }
-
- # Look for our key
- my $key_found = 0;
- my $new_authorized_keys_body = "";
- foreach my $line (split/\n/, $old_authorized_keys_body)
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { wrapper_script => $wrapper_script }});
+
+ my $shell_call = $wrapper_script." -i ".$public_key_file." -p ".$port." ".$user."@".$target;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }});
+
+ # Copy the public key to the target
+ my ($output, $return_code) = $anvil->System->call({secure => 1, shell_call => $shell_call});
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { output => $output, return_code => $return_code }});
+
+ if (($wrapper_script) && (-e $wrapper_script))
{
- $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }});
- if ($line eq $rsa_key)
- {
- $key_found = 1;
- $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { key_found => $key_found }});
- last;
- }
- $new_authorized_keys_body .= $line."\n";
+ unlink $wrapper_script;
}
-
- if (not $key_found)
+
+ if ($return_code)
{
- # Append our key.
- $new_authorized_keys_body .= $rsa_key."\n";
-
- # Write out the new file.
- my $problem = $anvil->Storage->write_file({
- debug => $debug,
- backup => 1,
- file => $target_authorized_keys_file,
- body => $new_authorized_keys_body,
- group => $user,
- mode => "0644",
- overwrite => 1,
- port => $port,
- password => $this_password,
- target => $target,
- user => $user,
- remote_user => $user,
- });
- if ($problem)
- {
- # Failed.
- $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0194", variables => {
- target => $target,
- file => $target_authorized_keys_file,
- }});
- return(0);
- }
-
- # Try to connect again, without a password this time.
- my $access = $anvil->Remote->test_access({
- debug => $debug,
- 'close' => $close,
- password => "",
- port => $port,
- target => $target,
- user => $user,
- });
- $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { access => $access }});
-
- # Did we get access?
- if ($access)
+ if ($output =~ / All keys were skipped because they already exist on the remote system/s)
{
- # Success!
- $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0195", variables => { target => $target }});
- return($access);
+ # Continue to retry because the key exists on the target.
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0176", variables => { target => $target }});
}
else
{
- # Welp, we tried.
- $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0196", variables => { target => $target }});
- return($access);
+ # Log and stop here because adding the key failed with a different reason.
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0194", variables => { target => $target }});
+ return(0);
}
}
+
+ # Try to connect again, without a password this time.
+ my $access = $anvil->Remote->test_access({
+ debug => $debug,
+ 'close' => $close,
+ password => "",
+ port => $port,
+ target => $target,
+ user => $user,
+ });
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { access => $access }});
+
+ # Did we get access?
+ if ($access)
+ {
+ # Success!
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0195", variables => { target => $target }});
+ return($access);
+ }
+ else
+ {
+ # Welp, we tried.
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0196", variables => { target => $target }});
+ return($access);
+ }
}
else
{
diff --git a/Anvil/Tools/Storage.pm b/Anvil/Tools/Storage.pm
index 656b805b9..cdbfa2327 100644
--- a/Anvil/Tools/Storage.pm
+++ b/Anvil/Tools/Storage.pm
@@ -48,7 +48,6 @@ my $THIS_FILE = "Storage.pm";
# update_config
# update_file
# write_file
-# _create_rsync_wrapper
# _wait_if_changing
=pod
@@ -4882,7 +4881,7 @@ sub rsync
if ($password)
{
# Remote target, wrapper needed.
- $wrapper_script = $anvil->Storage->_create_rsync_wrapper({
+ $wrapper_script = $anvil->System->create_rsync_wrapper({
debug => $debug,
target => $target,
password => $password,
@@ -6052,83 +6051,6 @@ fi";
#############################################################################################################
-=head2 _create_rsync_wrapper
-
-This does the actual work of creating the C<< expect >> wrapper script and returns the path to that wrapper for C<< rsync >> calls.
-
-If there is a problem, an empty string will be returned.
-
-Parameters;
-
-=head3 target (required)
-
-This is the IP address or (resolvable) host name of the remote machine.
-
-=head3 password (required)
-
-This is the password of the user you will be connecting to the remote machine as.
-
-=cut
-sub _create_rsync_wrapper
-{
- my $self = shift;
- my $parameter = shift;
- my $anvil = $self->parent;
- my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
- $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Storage->_create_rsync_wrapper()" }});
-
- # Check my parameters.
- my $target = defined $parameter->{target} ? $parameter->{target} : "";
- my $password = defined $parameter->{password} ? $parameter->{password} : "";
- $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
- password => $anvil->Log->is_secure($password),
- target => $target,
- }});
-
- if (not $target)
- {
- $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->_create_rsync_wrapper()", parameter => "target" }});
- return("");
- }
- if (not $password)
- {
- $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->_create_rsync_wrapper()", parameter => "password" }});
- return("");
- }
-
- ### NOTE: The first line needs to be the '#!...' line, hence the odd formatting below.
- my $timeout = 3600;
- my $wrapper_script = "/tmp/rsync.$target";
- my $wrapper_body = "#!".$anvil->data->{path}{exe}{expect}."
-set timeout ".$timeout."
-eval spawn rsync \$argv
-expect \"password:\" \{ send \"".$password."\\n\" \}
-expect eof
-";
- $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
- wrapper_script => $wrapper_script,
- wrapper_body => $wrapper_body,
- }});
- $anvil->Storage->write_file({
- debug => $debug,
- body => $wrapper_body,
- file => $wrapper_script,
- mode => "0700",
- overwrite => 1,
- secure => 1,
- });
-
- if (not -e $wrapper_script)
- {
- # Failed!
- $wrapper_script = "";
- $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 0, list => { wrapper_script => $wrapper_script }});
- }
-
- return($wrapper_script);
-}
-
-
=head3 _wait_if_changing
This takes a full path to a file, and watches it for at specified number of seconds to see if the size is changing. If it is, this method waits until the file size stops changing.
@@ -6154,7 +6076,7 @@ sub _wait_if_changing
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
- $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Storage->_create_rsync_wrapper()" }});
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Storage->_wait_if_changing()" }});
# Check my parameters.
my $file = defined $parameter->{file} ? $parameter->{file} : "";
diff --git a/Anvil/Tools/System.pm b/Anvil/Tools/System.pm
index 06577cac4..4bbb0cef0 100644
--- a/Anvil/Tools/System.pm
+++ b/Anvil/Tools/System.pm
@@ -14,6 +14,7 @@ use JSON;
use Text::Diff;
use String::ShellQuote;
use Encode;
+use File::Basename;
our $VERSION = "3.0.0";
my $THIS_FILE = "System.pm";
@@ -31,6 +32,8 @@ my $THIS_FILE = "System.pm";
# check_storage
# collect_ipmi_data
# configure_ipmi
+# create_rsync_wrapper
+# create_ssh_copy_id_wrapper
# disable_daemon
# enable_daemon
# find_matching_ip
@@ -50,7 +53,9 @@ my $THIS_FILE = "System.pm";
# stty_echo
# update_hosts
# wait_on_dnf
+# _abort_if_ci
# _check_anvil_conf
+# _create_wrapper_with_expect
# _load_firewalld_zones
# _load_specific_firewalld_zone
# _match_port_to_service
@@ -1082,7 +1087,7 @@ sub check_ssh_keys
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
- my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
+ my $debug = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->check_ssh_keys()" }});
# We do a couple things here. First we make sure our user's keys are up to date and stored in the
@@ -3221,6 +3226,72 @@ LIMIT 1
return($host_ipmi);
}
+=head2 create_rsync_wrapper
+
+This creates the C<< expect >> wrapper script and returns the path to that wrapper for C<< rsync >> calls.
+
+See System->_create_wrapper_with_expect() for details.
+
+Parameters;
+
+=head3 password (required)
+
+This is the password of the user you will be connecting to the remote machine as.
+
+=head3 target (optional)
+
+This is the IP address or (resolvable) host name of the remote machine.
+
+This is suppied to C<< id >> in System->_create_wrapper_with_expect().
+
+=cut
+sub create_rsync_wrapper
+{
+ my $self = shift;
+ my $parameter = shift;
+ my $anvil = $self->parent;
+ my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->create_rsync_wrapper()" }});
+
+ $parameter->{exe} = $anvil->data->{path}{exe}{'rsync'};
+ $parameter->{id} = $parameter->{target};
+
+ return $anvil->System->_create_wrapper_with_expect($parameter);
+}
+
+=head2 create_ssh_copy_id_wrapper
+
+This creates the C<< expect >> wrapper script and returns the path to that wrapper for C<< ssh-copy-id >> calls.
+
+See System->_create_wrapper_with_expect() for details.
+
+Parameters;
+
+=head3 password (required)
+
+This is the password of the user you will be connecting to the remote machine as.
+
+=head3 target (optional)
+
+This is the IP address or (resolvable) host name of the remote machine.
+
+This is suppied to C<< id >> in System->_create_wrapper_with_expect().
+
+=cut
+sub create_ssh_copy_id_wrapper
+{
+ my $self = shift;
+ my $parameter = shift;
+ my $anvil = $self->parent;
+ my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->create_ssh_copy_id_wrapper()" }});
+
+ $parameter->{exe} = $anvil->data->{path}{exe}{'ssh-copy-id'};
+ $parameter->{id} = $parameter->{target};
+
+ return $anvil->System->_create_wrapper_with_expect($parameter);
+}
+
=head2 disable_daemon
This method disables a daemon. The return code from the disable request will be returned.
@@ -4276,7 +4347,7 @@ sub manage_authorized_keys
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
- my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
+ my $debug = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->manage_authorized_keys()" }});
my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : "";
@@ -6245,6 +6316,96 @@ sub _check_anvil_conf
return(0);
}
+=head2 _create_wrapper_with_expect
+
+This does the actual work of creating the C<< expect >> wrapper script and returns the path to that wrapper for C<< exe >> calls.
+
+If there is a problem, an empty string will be returned.
+
+This should NOT be called directly, use it with something like create_rsync_wrapper or create_ssh_copy_id_wrapper
+
+Parameters;
+
+=head3 exe (required)
+
+This is the executable to wrap with expect.
+
+=head3 password (required)
+
+This is the password to supply to the C<< exe >>.
+
+=head3 id (optional)
+
+This is the wrapper's readable identifier.
+
+For example, it can be the IP address or (resolvable) host name of the remote machine when creating a rsync wrapper.
+
+=cut
+sub _create_wrapper_with_expect
+{
+ my $self = shift;
+ my $parameter = shift;
+ my $anvil = $self->parent;
+ my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->_create_wrapper_with_expect()" }});
+
+ # Check my parameters.
+ my $exe = defined $parameter->{exe} ? $parameter->{exe} : "";
+ my $id = defined $parameter->{id} ? $parameter->{id} : "";
+ my $password = defined $parameter->{password} ? $parameter->{password} : "";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ exe => $exe,
+ id => $id,
+ password => $anvil->Log->is_secure($password),
+ }});
+
+ if (not $exe)
+ {
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "System->_create_wrapper_with_expect()", parameter => "target" }});
+ return("");
+ }
+ if (not $password)
+ {
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "System->_create_wrapper_with_expect()", parameter => "password" }});
+ return("");
+ }
+
+ my $exe_name = basename($exe);
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { exe_name => $exe_name }});
+
+ ### NOTE: The first line needs to be the '#!...' line, hence the odd formatting below.
+ my $timeout = 3600;
+ my $wrapper_script = $anvil->data->{path}{directories}{tmp}."/".$exe_name.".".$id;
+ my $wrapper_body = "#!".$anvil->data->{path}{exe}{expect}."
+set timeout ".$timeout."
+eval spawn ".$exe." \$argv
+expect \"password:\" \{ send \"".$password."\\n\" \}
+expect eof
+";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ wrapper_script => $wrapper_script,
+ wrapper_body => $wrapper_body,
+ }});
+ $anvil->Storage->write_file({
+ debug => $debug,
+ backup => 0,
+ body => $wrapper_body,
+ file => $wrapper_script,
+ mode => "0700",
+ overwrite => 1,
+ secure => 1,
+ });
+
+ if (not -e $wrapper_script)
+ {
+ # Failed!
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 0, list => { wrapper_script => $wrapper_script }});
+ return("");
+ }
+
+ return($wrapper_script);
+}
+
=head2 _load_firewalld_zones
This reads in the XML files for all of the firewalld zones.
diff --git a/anvil.spec.in b/anvil.spec.in
index c8a546829..8b81b0de8 100644
--- a/anvil.spec.in
+++ b/anvil.spec.in
@@ -10,6 +10,7 @@
# 2 backslashes to make shell continue the line, and
# 1 more backslash to make rpmbuild continue the line.
%define coreservices anvil-daemon.service \\\
+anvil-monitor-auth-keys.path \\\
anvil-monitor-network.service \\\
scancore.service
### This adds a lot of noise to anvil.log and likely only useful in rare debug
@@ -289,6 +290,15 @@ setenforce 0
# enable and start required core services on fresh install
if [ $1 -eq 1 ]
then
+ # write a permanent rule in audit.rules to monitor /root/.ssh/authorized_keys
+ if [ ! -e '/etc/audit/rules.d/anvil-monitor-auth-keys.rules' ]
+ then
+ echo "-a always,exit -F arch=b64 -F path=/root/.ssh/authorized_keys -F perm=wa" >/etc/audit/rules.d/anvil-monitor-auth-keys.rules
+ chmod 0600 /etc/audit/rules.d/anvil-monitor-auth-keys.rules
+ restorecon /etc/audit/rules.d/anvil-monitor-auth-keys.rules
+ augenrules --load
+ fi
+
systemctl enable --now chronyd.service
systemctl enable --now %coreservices
fi
@@ -393,6 +403,17 @@ touch /etc/anvil/type.dr
# Only uninstall our services
%systemd_preun %coreservices
+# on uninstall (not upgrade)...
+if [ $1 -eq 0 ]
+then
+ # remove the rule that monitors /root/.ssh/authorized_keys
+ if [ -e '/etc/audit/rules.d/anvil-monitor-auth-keys.rules' ]
+ then
+ rm -f /etc/audit/rules.d/anvil-monitor-auth-keys.rules
+ augenrules --load
+ fi
+fi
+
### Remove stuff - Disabled for now, messes things up during upgrades
%postun core
diff --git a/selinux/anvil-subnode.te.in b/selinux/anvil-subnode.te.in
index 95f21fad1..5925fba5c 100644
--- a/selinux/anvil-subnode.te.in
+++ b/selinux/anvil-subnode.te.in
@@ -14,9 +14,11 @@ policy_module(anvil-subnode, 1.1.0)
# Use existing types; don't declare unless it's new.
#
require {
+ type init_t;
type mnt_t;
- type sysctl_vm_t;
+ type ssh_home_t;
type svirt_t;
+ type sysctl_vm_t;
type virsh_t;
class file { getattr open read };
}
@@ -26,6 +28,11 @@ require {
# drbd rules will be provided by drbd-utils package.
+#============= init_t ==============
+# allow systemd to monitor /root/.ssh/authorized_keys with path unit
+allow init_t ssh_home_t:file { read };
+
+
#============= virsh_t ==============
# Needed for virsh to access the domain XMLs under /mnt.
allow virsh_t mnt_t:file { open read };
diff --git a/share/words.xml b/share/words.xml
index ec14e2f22..6b033d62e 100644
--- a/share/words.xml
+++ b/share/words.xml
@@ -2090,7 +2090,7 @@ The database connection error was:
- The password: [#!variable!password!#] was successfully used to connect to the host: [#!variable!target!#], however the target user's authorized keys file: [#!variable!file!#] was not read. As such, we can not setup passwordless SSH. Given the initial connection test was without password, we will return '0' to indicate no access.
+ It looks like the attempt to add our RSA key to: [#!variable!target!#] failed because the key already exists. We'll try to connect again.
No cookies were read, the use is not logged in.
The user's UUID: [#!variable!uuid!#] was read, but it didn't match any known users.
The user has been logged out.
@@ -2108,7 +2108,7 @@ The database connection error was:
Connection only to: [#!variable!db_uuid!#], skipping: [#!variable!uuid!#].
The connection to the database: [#!variable!server!#] has failed. Will attempt to reconnect to the database(s).
The public RSA key appears to have not been read properly. Read the key: [#!variable!key!#] from the file: [#!variable!file!#]. Expected the key to start with 'ssh-rsa '.
- It looks like the attempt to add our RSA key to: [#!variable!file!#] on: [#!variable!target!#] failed. Returning '0' as we failed to connect using no password, as originally called.
+ It looks like the attempt to add our RSA key to: [#!variable!target!#] failed. Returning '0' as we failed to connect using no password, as originally called.
Successfully connected to: [#!variable!target!#] without a password!
Despite adding our key, we seemed to have failed to connect to: [#!variable!target!#] without a password, as originall requested.
maintenance_mode() was passed an invalid 'set' value: [#!variable!set!#]. No action taken.]]>
diff --git a/units/Makefile.am b/units/Makefile.am
index e0443923f..0c38f2a7a 100644
--- a/units/Makefile.am
+++ b/units/Makefile.am
@@ -3,6 +3,8 @@ MAINTAINERCLEANFILES = Makefile.in
servicedir = $(SYSTEMD_UNIT_DIR)
dist_service_DATA = \
anvil-daemon.service \
+ anvil-monitor-auth-keys.path \
+ anvil-monitor-auth-keys.service \
anvil-monitor-daemons.service \
anvil-monitor-drbd.service \
anvil-monitor-lvm.service \
diff --git a/units/anvil-monitor-auth-keys.path b/units/anvil-monitor-auth-keys.path
new file mode 100644
index 000000000..abd18a114
--- /dev/null
+++ b/units/anvil-monitor-auth-keys.path
@@ -0,0 +1,11 @@
+[Unit]
+Description=Triggers a service to collect info on /root/.ssh/authorized_keys when it's modified
+After=local-fs.target
+Before=sysinit.target
+
+[Path]
+PathModified=/root/.ssh/authorized_keys
+TriggerLimitBurst=0
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/units/anvil-monitor-auth-keys.service b/units/anvil-monitor-auth-keys.service
new file mode 100644
index 000000000..801ed9b6e
--- /dev/null
+++ b/units/anvil-monitor-auth-keys.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Collects info on /root/.ssh/authorized_keys
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/stat /root/.ssh/authorized_keys
+ExecStart=/usr/bin/echo "----- START BODY -----"
+ExecStart=/usr/bin/cat /root/.ssh/authorized_keys
+ExecStart=/usr/bin/echo "----- END BODY -----"
+StandardOutput=append:/var/log/anvil.log
+StandardError=append:/var/log/anvil.log
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file