From 40ff917d73ab60d9413700a5ec45cfdcc478a9fd Mon Sep 17 00:00:00 2001 From: Nicolas Charles Date: Thu, 23 Aug 2018 17:28:12 +0200 Subject: [PATCH] Work in progress --- techniques/system/common/1.0/hooks.st | 353 ++++++++++++++++++ .../sshKeyDistribution/4.0/metadata.xml | 21 +- .../4.0/sshKeyDistribution.st | 106 +++--- 3 files changed, 428 insertions(+), 52 deletions(-) diff --git a/techniques/system/common/1.0/hooks.st b/techniques/system/common/1.0/hooks.st index dc09d0c90..5bc8ef92b 100644 --- a/techniques/system/common/1.0/hooks.st +++ b/techniques/system/common/1.0/hooks.st @@ -350,3 +350,356 @@ bundle agent runhook_repoGpgKeyManagementGetKeys(json) { contain => outputable, module => "true"; } + + +######################################################## +## SSH authorised keys specific hooks +######################################################## + + +# Create the temporary ssh key files for all users, and create classes for all users that need their keys enforce +# in the format ssh_key_distribution__to_flush , and check if user is present +# with class ssh_key_distribution_user__exists +# It also computes, for each user, +# - the homedir homedir[] +# - the gid gid[] +# It also contains the list of user to manage keys, in list "userlist" +# And create empty temp key files in /var/rudder/tmp/check_ssh_key_distribution/ (${temp_ssh_key_path}/${userlist}.authorized_keys.tmp) +bundle agent runhook_sshKeyDistribution_pre_hook(json) { + vars: + "definitions" data => parsejson("${json}"); + + "reporting" data => mergedata("definitions[reports]"); + + "reportkeys" slist => getindices("reporting"); + + + # Here we find all the bundles that are tagged as check_ssh_key_distribution_technique + # we need to remove the default: from the variable name, as it causes issues if used in variable name definition + # Note: I cannot remove the defaults: from the bundlesmatching directly + "raw_bundles" slist => bundlesmatching("default:check_ssh_key_distribution_.*", "check_ssh_key_distribution_technique"); + "bundles" slist => maplist(regex_replace("${this}", "default:", "", ""), "@{raw_bundles}"); + + + # get all indices for each of the bundle + "array[${bundles}]" slist => getindices("${bundles}.sshkey_distribution_name"); + + # get the list of all users to manage in list userlist + # first, we put them in array (and ${bundles}_${array[${bundles}]} is canonified, so a valid name) + "data_userlist[${bundles}_${array[${bundles}]}]" string => "${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}"; + + "userlist" slist => getvalues("data_userlist"); + + + # Path to the temporary files created for the checking the files + "temp_ssh_key_path" string => "/var/rudder/tmp/check_ssh_key_distribution/"; + + # get the home dir for each users + # Only Linuxes (not Slackware), Solaris and FreeBSD support PAM/getent + pass1.(linux.!slackware)|solaris|freebsd:: + + "userdata_${userlist}" + string => execresult("/usr/bin/getent passwd ${userlist}", "noshell"); + + # On systems without PAM, directly read entries from /etc/passwd instead (compatibility) + pass1.!((linux.!slackware)|solaris|freebsd):: + + "userdata_${userlist}" + string => execresult("/usr/bin/grep ^${userlist}: /etc/passwd", "noshell"); + + pass2:: + "no_${userlist}" + int => parsestringarray("userarray_${userlist}", "${userdata_${userlist}}", "", ":", "1000", "200000" ), + ifvarclass => "ssh_key_distribution_user_${userlist}_exists"; + + "homedir[${userlist}]" + string => "${userarray_${userlist}[${userlist}][5]}", + ifvarclass => "ssh_key_distribution_user_${userlist}_exists"; + + "gid[${userlist}]" + string => "${userarray_${userlist}[${userlist}][3]}", + ifvarclass => "ssh_key_distribution_user_${userlist}_exists"; + + classes: + pass1:: + # check if user is present on the system + "ssh_key_distribution_user_${userlist}_exists" expression => userexists("${userlist}"), + scope => "namespace"; + + + "ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush" expression => strcmp("${${bundles}.sshkey_distribution_edit_type[${array[${bundles}]}]}", "true"), + scope => "namespace"; + + any:: + "pass2" expression => "pass1"; + "pass1" expression => "any"; + + + files: + pass2:: + "${temp_ssh_key_path}/${userlist}.authorized_keys.tmp" + create => "true", + edit_line => delete_lines_matching(".*"), + perms => mog("700", "${userlist}", "${runhook_sshKeyDistribution_pre_hook.gid[${userlist}]}"), + ifvarclass => "ssh_key_distribution_user_${userlist}_exists"; + + + + +reports: +pass2:: +" ssh_key_distribution_user_${userlist}_exists" ifvarclass => "ssh_key_distribution_user_${userlist}_exists"; + +} + + + + + +# Post hook for ssh Key Distribution Technique +# Compares the ssh key file with the temporary one - if the considered file as changed, and flush is set, and any instance for this user is in enforce, replace the file + +# It also do the last report for the directive, for flush per user +bundle agent runhook_sshKeyDistribution_post_hook(json) { + vars: + "definitions" data => parsejson("${json}"); + + "reporting" data => mergedata("definitions[reports]"); + + "reportkeys" slist => getindices("reporting"); + + + # get size of generated tmp files + "tmp_ssh_file_size_${runhook_sshKeyDistribution_pre_hook.userlist}" int => filesize("${runhook_sshKeyDistribution_pre_hook.temp_ssh_key_path}/${runhook_sshKeyDistribution_pre_hook.userlist}.authorized_keys.tmp"), + ifvarclass => "ssh_key_distribution_user_${runhook_sshKeyDistribution_pre_hook.userlist}_exists"; + + + # get the size of the ssh key + "ssh_file_size_${runhook_sshKeyDistribution_pre_hook.userlist}" int => filesize("${runhook_sshKeyDistribution_pre_hook.homedir[${runhook_sshKeyDistribution_pre_hook.userlist}]}/.ssh/authorized_keys"), + ifvarclass => "ssh_key_distribution_user_${runhook_sshKeyDistribution_pre_hook.userlist}_exists"; + + + # DETECT IF THE USER IS IN AUDIT OR IN ENFORCE + # Here we find all the bundles that are tagged as check_ssh_key_distribution_technique + "raw_bundles" slist => bundlesmatching("default:check_ssh_key_distribution_.*", "check_ssh_key_distribution_technique"); + "bundles" slist => maplist(regex_replace("${this}", "default:", "", ""), "@{raw_bundles}"); + + # get all indices for each of the bundle + "array[${bundles}]" slist => getindices("${bundles}.sshkey_distribution_name"); + "audit_mode[${bundles}]" string => "${${bundles}.is_audit_mode}"; + + # get the iterator over everything + # ${bundles}.sshkey_distribution_name[${array[${bundles}]}] iterates over every name of reporting + + # Defines reporting status, based on ${audit_mode[${bundles}]} + "success[false]" string => "result_success"; + "success[true]" string => "audit_compliant"; + "na[false]" string => "result_na"; + "na[true]" string => "audit_na"; + "repaired[false]" string => "result_repaired"; + "repaired[true]" string => "audit_noncompliant"; + "error[false]" string => "result_error"; + "error[true]" string => "audit_noncompliant"; + + "technique_name" string => "sshKeyDistribution"; + "component_name" string => "Flush SSH file"; + + classes: + # check if files have the same size + "same_size_for_${runhook_sshKeyDistribution_pre_hook.userlist}" expression => strcmp("${tmp_ssh_file_size_${runhook_sshKeyDistribution_pre_hook.userlist}}", "${ssh_file_size_${runhook_sshKeyDistribution_pre_hook.userlist}}"); + + + # Identical files are if: + # No error/repair for a user + identical file size + "no_change_on_ssh_file_for_${runhook_sshKeyDistribution_pre_hook.userlist}" expression => "same_size_for_${runhook_sshKeyDistribution_pre_hook.userlist}.!(check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_repaired|check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_error)", + ifvarclass => "ssh_key_distribution_user_${runhook_sshKeyDistribution_pre_hook.userlist}_exists"; + + # file differents and No error/repair for a user -> maybe need to flush + "same_keys_different_size_ssh_file_for_${runhook_sshKeyDistribution_pre_hook.userlist}" expression => "!same_size_for_${runhook_sshKeyDistribution_pre_hook.userlist}.!(check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_repaired|check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_error)", + ifvarclass => "ssh_key_distribution_user_${runhook_sshKeyDistribution_pre_hook.userlist}_exists"; + + # same sizes, but repaired -> could be that we repaired, and no need to flush - this will be settled when the copy of file will be done + "same_size_but_repaired_keys_${runhook_sshKeyDistribution_pre_hook.userlist}" expression => "same_size_for_${runhook_sshKeyDistribution_pre_hook.userlist}.(check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_repaired|check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_error)", + ifvarclass => "ssh_key_distribution_user_${runhook_sshKeyDistribution_pre_hook.userlist}_exists"; + + # different size and repaired/error -> yeah, we repaired + "ssh_keys_repaired_${runhook_sshKeyDistribution_pre_hook.userlist}" expression => "!same_size_for_${runhook_sshKeyDistribution_pre_hook.userlist}.(check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_repaired|check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_error)", + ifvarclass => "ssh_key_distribution_user_${runhook_sshKeyDistribution_pre_hook.userlist}_exists"; + + + # define if there is at least one enforce + "is_enforce" expression => strcmp("${reporting[${reportkeys}][mode]}", "enforce"); + + # For reporting, detect each enforce/audit + "is_enforce_${reportkeys}" expression => strcmp("${reporting[${reportkeys}][mode]}", "enforce"); + + + # define for each bundle a class if in enforce + "enforce_mode_${bundles}" expression => strcmp("${audit_mode[${bundles}]}", "false"); + + # define the class "enforce_ssh_key_distribution__to_flush" for bundle not in audit mode, where we want to flush + "enforce_ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush" expression => strcmp("${${bundles}.sshkey_distribution_edit_type[${array[${bundles}]}]}", "true"), + ifvarclass => "enforce_mode_${bundles}"; + + # audit is for all user that are to flush, but not the class enforce_ssh_key_distribution__to_flush set + "audit_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush" expression => "any", + ifvarclass => "!enforce_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush.ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush"; + + + + + any:: + "pass2" expression => "pass1"; + "pass1" expression => "any"; + + + methods: + pass2:: + # Do the reporting for the post hook + # User not present + "No user" usebundle => rudder_common_report( + "${technique_name}", "result_error", + "${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}", "${component_name}", "${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}", + "The user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} does not exists on this system, impossible to flush keys" + ), + ifvarclass => "!ssh_key_distribution_user_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_exists.enforce_mode_${bundles}.ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush"; + + # User not present + "No user" usebundle => rudder_common_report( + "${technique_name}", "result_error", + "${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}", "${component_name}", "${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}", + "The user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} does not exists on this system" + ), + ifvarclass => "!ssh_key_distribution_user_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_exists.enforce_mode_${bundles}.!ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush"; + + "No user" usebundle => rudder_common_report( + "${technique_name}", "audit_noncompliant", + "${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}", "${component_name}", "${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}", + "The user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} does not exists on this system" + ), + ifvarclass => "!ssh_key_distribution_user_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_exists.!enforce_mode_${bundles}"; + + # No flush requested -> NA + "No flush" usebundle => rudder_common_report( + "${technique_name}", "${na[${audit_mode[${bundles}]}]}", + "${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}", "${component_name}", "${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}", + "The keys for user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} were not requested to be flush" + ), + ifvarclass => "ssh_key_distribution_user_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_exists.!ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush"; + + # File didn't change + no change -> success + "No change on user" usebundle => rudder_common_report( + "${technique_name}", "${success[${audit_mode[${bundles}]}]}", + "${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}", "${component_name}", "${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}", + "The keys for user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} were all correctly set" + ), + ifvarclass => "ssh_key_distribution_user_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_exists.ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush.no_change_on_ssh_file_for_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}"; + + # We managed all the cases were we didn't actually try to copy the file. Now we handle reporting based on copy of temp file to final file + + # File didn't change -> success + "No change on file" usebundle => rudder_common_report( + "${technique_name}", "${success[${audit_mode[${bundles}]}]}", + "${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}", "${component_name}", "${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}", + "The list of keys for user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} were correctly set" + ), + ifvarclass => "post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_kept.!post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_repaired"; + + # File changed - repaired (or non compliant) - but if repaired, it means it's been fixed + "changes on file" usebundle => rudder_common_report( + "${technique_name}", "${repaired[${audit_mode[${bundles}]}]}", + "${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}", "${component_name}", "${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}", + "The keys for user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} were flushed" + ), + ifvarclass => "post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_repaired.!post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_error"; + + # File could not be changed - repaired (or non compliant) - but if repaired, it means it's been fixed + "error on file" usebundle => rudder_common_report( + "${technique_name}", "${error[${audit_mode[${bundles}]}]}", + "${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}", "${component_name}", "${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}", + "The keys for user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} could not be flushed" + ), + ifvarclass => "post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_error.!post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_repaired"; + + + + files: + # replace file if size if same_keys_different_size_ssh_file_for_ or ssh_keys_repaired_ or same_size_but_repaired_keys_ and ssh_key_distribution__to_flush, and in ENFORCE (we need to know when enforce) + "${runhook_sshKeyDistribution_pre_hook.homedir[${runhook_sshKeyDistribution_pre_hook.userlist}]}/.ssh/authorized_keys" + copy_from => copy_digest("${runhook_sshKeyDistribution_pre_hook.temp_ssh_key_path}/${runhook_sshKeyDistribution_pre_hook.userlist}.authorized_keys.tmp"), + classes => classes_generic("post_hook_flush_ssh_${runhook_sshKeyDistribution_pre_hook.userlist}"), + comment => "IN ENFORCE", + ifvarclass => "enforce_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush.(same_keys_different_size_ssh_file_for_${runhook_sshKeyDistribution_pre_hook.userlist}|ssh_keys_repaired_${runhook_sshKeyDistribution_pre_hook.userlist}|same_size_but_repaired_keys_${runhook_sshKeyDistribution_pre_hook.userlist})"; + + + # auditing file replacement + "${runhook_sshKeyDistribution_pre_hook.homedir[${runhook_sshKeyDistribution_pre_hook.userlist}]}/.ssh/authorized_keys" + copy_from => copy_digest("${runhook_sshKeyDistribution_pre_hook.temp_ssh_key_path}/${runhook_sshKeyDistribution_pre_hook.userlist}.authorized_keys.tmp"), + action => WarnOnly, + classes => classes_generic("post_hook_flush_ssh_${runhook_sshKeyDistribution_pre_hook.userlist}"), + ifvarclass => "audit_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush.(same_keys_different_size_ssh_file_for_${runhook_sshKeyDistribution_pre_hook.userlist}|ssh_keys_repaired_${runhook_sshKeyDistribution_pre_hook.userlist}|same_size_but_repaired_keys_${runhook_sshKeyDistribution_pre_hook.userlist})"; + + + # finally, delete the files +# "${runhook_sshKeyDistribution_pre_hook.temp_ssh_key_path}/${runhook_sshKeyDistribution_pre_hook.userlist}.authorized_keys.tmp" +# delete => tidy; + + + reports: +"file ${tmp_ssh_file_size_${runhook_sshKeyDistribution_pre_hook.userlist}}"; +" and ${ssh_file_size_${runhook_sshKeyDistribution_pre_hook.userlist}}" ; +"file is ${runhook_sshKeyDistribution_pre_hook.homedir[${runhook_sshKeyDistribution_pre_hook.userlist}]}/.ssh/authorized_keys" ; + +" No change for no_change_on_ssh_file_for_${runhook_sshKeyDistribution_pre_hook.userlist}" +ifvarclass => "no_change_on_ssh_file_for_${runhook_sshKeyDistribution_pre_hook.userlist}" ; + + +" Same size for same_size_for_${runhook_sshKeyDistribution_pre_hook.userlist}" +ifvarclass => "same_size_for_${runhook_sshKeyDistribution_pre_hook.userlist}" ; + +" no repaired for check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_repaired " +ifvarclass => "!check_ssh_key_distribution_user_key_${runhook_sshKeyDistribution_pre_hook.userlist}_repaired" ; + + " for ${bundles} audit is ${${bundles}.is_audit_mode} (${audit_mode[${bundles}]} )" ; +" for ${bundles} , edit type is ${${bundles}.sshkey_distribution_edit_type[${array[${bundles}]}]} "; +" for ${bundles} enforce mode is set " +ifvarclass => "enforce_mode_${bundles}"; + +" final file is ${runhook_sshKeyDistribution_pre_hook.homedir[${runhook_sshKeyDistribution_pre_hook.userlist}]}/.ssh/authorized_keys"; +" it if ${runhook_sshKeyDistribution_pre_hook.homedir[${runhook_sshKeyDistribution_pre_hook.userlist}]}/.ssh/authorized_keys ifvarclass" +ifvarclass => "enforce_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush"; + +"Just for reporting, user is ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} and report id ${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]}"; + +" defined user list ${runhook_sshKeyDistribution_pre_hook.userlist}" ; + + +" success for enforce flushing for user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} with report id ${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]} (tag is ${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}" +ifvarclass => "enforce_ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush.post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_kept"; + +" repaired for enforce flushing for user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} with report id ${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]} (tag is ${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}" +ifvarclass => "enforce_ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush.post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_repaired"; + +" error for enforce flushing for user ${${bundles}.sshkey_distribution_name[${array[${bundles}]}]} with report id ${${bundles}.sshkey_distribution_uuid[${array[${bundles}]}]} (tag is ${${bundles}.sshkey_distribution_tag[${array[${bundles}]}]}" +ifvarclass => "enforce_ssh_key_distribution_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_to_flush.post_hook_flush_ssh_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}_error"; + + +"reporting is ${reporting}"; +"reportkeys is ${reportkeys}"; + +" flushing file for user ${runhook_sshKeyDistribution_pre_hook.userlist} " +ifvarclass => "enforce_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush"; + +" auditing flushing file for user ${runhook_sshKeyDistribution_pre_hook.userlist} " +ifvarclass => "audit_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush"; + + +" neither flushing nor enforcing for ${runhook_sshKeyDistribution_pre_hook.userlist} " +ifvarclass => "!enforce_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush.!audit_ssh_key_distribution_${runhook_sshKeyDistribution_pre_hook.userlist}_to_flush"; + + +"user exists, of course ${runhook_sshKeyDistribution_pre_hook.userlist} " +ifvarclass => "ssh_key_distribution_user_${runhook_sshKeyDistribution_pre_hook.userlist}_exists"; + + +} diff --git a/techniques/systemSettings/remoteAccess/sshKeyDistribution/4.0/metadata.xml b/techniques/systemSettings/remoteAccess/sshKeyDistribution/4.0/metadata.xml index 9462322e4..a1cdb04bd 100644 --- a/techniques/systemSettings/remoteAccess/sshKeyDistribution/4.0/metadata.xml +++ b/techniques/systemSettings/remoteAccess/sshKeyDistribution/4.0/metadata.xml @@ -21,26 +21,38 @@ along with this program. If not, see . RHEL / CentOS SuSE LES / DES / OpenSuSE AIX - cfengine-community + cfengine-community true + separated - check_ssh_key_distribution + check_ssh_key_distribution_RudderUniqueID + +
+      
+    
+ + + + +
+ SSH_KEY_DISTRIBUTION_NAME -
+
+
SSH_KEY_DISTRIBUTION_TAG Key tag (for tracking only) @@ -66,6 +78,8 @@ along with this program. If not, see . textarea +
+
SSH_KEY_DISTRIBUTION_EDIT_TYPE Remove other keys @@ -83,6 +97,7 @@ along with this program. If not, see .
+
diff --git a/techniques/systemSettings/remoteAccess/sshKeyDistribution/4.0/sshKeyDistribution.st b/techniques/systemSettings/remoteAccess/sshKeyDistribution/4.0/sshKeyDistribution.st index 880dea786..95b3dd32c 100644 --- a/techniques/systemSettings/remoteAccess/sshKeyDistribution/4.0/sshKeyDistribution.st +++ b/techniques/systemSettings/remoteAccess/sshKeyDistribution/4.0/sshKeyDistribution.st @@ -16,11 +16,28 @@ # ##################################################################################### -# Copyright (C) Normation +# Manage SSH Key - per user +# we need to know, for each user, if we want to completly enforce the list of keys or not. +# Available data: +# class: ssh_key_distribution__to_flush - if set, we asked to flush the userkey +# ssh_key_distribution_user_${userlist}_exists - if set, user exists on system +# var: runhook_sshKeyDistribution_pre_hook.userlist list of user set -bundle agent check_ssh_key_distribution + +# If we enforce the list, in audit or non audit +# Pre-hook: we create an empty file at first (.tmp or .rudder), and we put all the keys in the tmp file, as well as in the global file (second one is to know if key was already there or not) +# post-hook: +# if all keys are kept status, then we compare the file sizes. If equals, then all is kept (resp compliant). If differents, in non audit, we flush the ssh key with the .tmp file, and they are all repaired, in audit, they are all non compliant, with a message saying there are there, but there are extra keys +# if any is in repaired, in enforce then we copy the ssh key from the .tmp, and keep the status of each keys, in audit we keep the compliant/non compliant status + +# if we don't enforce the list, then we directly manage the ssh key + +bundle agent check_ssh_key_distribution_RudderUniqueID { + meta: + "tags" slist => { "check_ssh_key_distribution_technique" }; + vars: any:: @@ -28,7 +45,6 @@ bundle agent check_ssh_key_distribution "technique_name" string => "sshKeyDistribution"; "component_name" string => "SSH key"; - "config_basename" string => "authorized_keys"; &SSH_KEY_DISTRIBUTION_TAG:{key_tag |"sshkey_distribution_tag[&i&]" string => "&key_tag&"; }& @@ -40,18 +56,21 @@ bundle agent check_ssh_key_distribution }& &TRACKINGKEY:{uuid |"sshkey_distribution_uuid[&i&]" string => "&uuid&"; }& + "sshkey_distribution_index" slist => getindices("sshkey_distribution_name"); - "no_${sshkey_distribution_index}" - int => parsestringarray("userarray_${sshkey_distribution_index}", "${userdata_${sshkey_distribution_index}}", "", ":", "1000", "200000" ); + + "is_audit_mode" string => "false", + ifvarclass => "!(dry_run|global_dry_run)"; + + "is_audit_mode" string => "true", + ifvarclass => "dry_run|global_dry_run"; + "key_class_prefix[${sshkey_distribution_index}]" string => canonify("${sshkey_distribution_tag[${sshkey_distribution_index}]}_${sshkey_distribution_uuid[${sshkey_distribution_index}]}"); - "homedir[${sshkey_distribution_index}]" - string => "${userarray_${sshkey_distribution_index}[${sshkey_distribution_name[${sshkey_distribution_index}]}][5]}"; - "ssh_types" string => "ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-nistp521|ecdsa-sha2-nistp384|ecdsa-sha2-nistp256|ssh-dsa"; # Extract key content from the keys @@ -60,48 +79,37 @@ bundle agent check_ssh_key_distribution string => execresult("${paths.echo} '${sshkey_distribution_key[${sshkey_distribution_index}]}' | ${paths.sed} -E 's/(.*\s+)?(${ssh_types})\s+(\S+)(\s.*)?/\3/'", "useshell"), ifvarclass => "correct_ssh_key_format_${sshkey_distribution_index}"; - # Only Linuxes (not Slackware), Solaris and FreeBSD support PAM/getent - (linux.!slackware)|solaris|freebsd:: - - "userdata_${sshkey_distribution_index}" - string => execresult("/usr/bin/getent passwd ${sshkey_distribution_name[${sshkey_distribution_index}]}", "noshell"); - - # On systems without PAM, directly read entries from /etc/passwd instead (compatibility) - !((linux.!slackware)|solaris|freebsd):: - - "userdata_${sshkey_distribution_index}" - string => execresult("/usr/bin/grep ^${sshkey_distribution_name[${sshkey_distribution_index}]}: /etc/passwd", "noshell"); - - any:: - "gid[${sshkey_distribution_index}]" - string => "${userarray_${sshkey_distribution_index}[${sshkey_distribution_name[${sshkey_distribution_index}]}][3]}"; - classes: "pass2" expression => "pass1"; "pass1" expression => "any"; - "begin_evaluation" expression => isvariable("sshkey_distribution_index"); - pass2:: "correct_ssh_key_format_${sshkey_distribution_index}" expression => regcmp("(.*\s+)?(${ssh_types})\s+(\S+)(\s.*)?", "${sshkey_distribution_key[${sshkey_distribution_index}]}"); - begin_evaluation:: - "user_${sshkey_distribution_index}_exists" expression => userexists("${sshkey_distribution_name[${sshkey_distribution_index}]}"); + files: + pass2.!windows:: + "${runhook_sshKeyDistribution_pre_hook.homedir[${sshkey_distribution_name[${sshkey_distribution_index}]}]}/.ssh/." + create => "true", + perms => mog("700", "${sshkey_distribution_name[${sshkey_distribution_index}]}", "${runhook_sshKeyDistribution_pre_hook.gid[${sshkey_distribution_name[${sshkey_distribution_index}]}]}"), + ifvarclass => concat(canonify("ssh_key_distribution_user_${sshkey_distribution_name[${sshkey_distribution_index}]}_exists"),".correct_ssh_key_format_${sshkey_distribution_index}"); - !windows:: - "${homedir[${sshkey_distribution_index}]}/.ssh/." + # Caution, it can report both repaired and kept (permission ok + file repaired) + "${runhook_sshKeyDistribution_pre_hook.homedir[${sshkey_distribution_name[${sshkey_distribution_index}]}]}/.ssh/authorized_keys" create => "true", - perms => mog("700", "${sshkey_distribution_name[${sshkey_distribution_index}]}", "${gid[${sshkey_distribution_index}]}"), - ifvarclass => concat(canonify("user_${sshkey_distribution_index}_exists"),".correct_ssh_key_format_${sshkey_distribution_index}"); + perms => mog("600", "${sshkey_distribution_name[${sshkey_distribution_index}]}", "${runhook_sshKeyDistribution_pre_hook.gid[${sshkey_distribution_name[${sshkey_distribution_index}]}]}"), + edit_line => append_or_replace_ssh_key_RudderUniqueID("${sshkey_distribution_key[${sshkey_distribution_index}]}", "${sshkey_distribution_key_content_${sshkey_distribution_index}}", "${sshkey_distribution_index}"), +# should also define a class for the user + classes => classes_generic_two("${key_class_prefix[${sshkey_distribution_index}]}_RudderUniqueID", "check_ssh_key_distribution_user_key_${sshkey_distribution_name[${sshkey_distribution_index}]}"), + ifvarclass => concat(canonify("ssh_key_distribution_user_${sshkey_distribution_name[${sshkey_distribution_index}]}_exists"),".correct_ssh_key_format_${sshkey_distribution_index}"); + + # insert in temp file as well, inconditionnaly + "${runhook_sshKeyDistribution_pre_hook.temp_ssh_key_path}/${sshkey_distribution_name[${sshkey_distribution_index}]}.authorized_keys.tmp" + perms => m("600"), + edit_line => append_or_replace_ssh_key_RudderUniqueID("${sshkey_distribution_key[${sshkey_distribution_index}]}", "${sshkey_distribution_key_content_${sshkey_distribution_index}}", "${sshkey_distribution_index}"), + ifvarclass => concat(canonify("ssh_key_distribution_user_${sshkey_distribution_name[${sshkey_distribution_index}]}_exists"),".correct_ssh_key_format_${sshkey_distribution_index}"); + - "${homedir[${sshkey_distribution_index}]}/.ssh/${config_basename}" - create => "true", - edit_defaults => rudder_empty_select("${sshkey_distribution_edit_type[${sshkey_distribution_index}]}"), - perms => mog("600", "${sshkey_distribution_name[${sshkey_distribution_index}]}", "${gid[${sshkey_distribution_index}]}"), - edit_line => append_or_replace_ssh_key("${sshkey_distribution_key[${sshkey_distribution_index}]}", "${sshkey_distribution_key_content_${sshkey_distribution_index}}", "${sshkey_distribution_index}"), - classes => rudder_common_classes("${key_class_prefix[${sshkey_distribution_index}]}"), - ifvarclass => concat(canonify("user_${sshkey_distribution_index}_exists"),".correct_ssh_key_format_${sshkey_distribution_index}"); methods: @@ -109,11 +117,11 @@ bundle agent check_ssh_key_distribution "SSH Key Report" usebundle => rudder_common_reports_generic( - "${technique_name}", "${key_class_prefix[${sshkey_distribution_index}]}", + "${technique_name}", "${key_class_prefix[${sshkey_distribution_index}]}_RudderUniqueID", "${sshkey_distribution_uuid[${sshkey_distribution_index}]}", "${component_name}", "${sshkey_distribution_tag[${sshkey_distribution_index}]}", "SSH key \"${sshkey_distribution_tag[${sshkey_distribution_index}]}\" for user ${sshkey_distribution_name[${sshkey_distribution_index}]}" ), - ifvarclass => "user_${sshkey_distribution_index}_exists.correct_ssh_key_format_${sshkey_distribution_index}"; + ifvarclass => "ssh_key_distribution_user_${sshkey_distribution_name[${sshkey_distribution_index}]}_exists.correct_ssh_key_format_${sshkey_distribution_index}"; "Wrong SSH Key Format Report" usebundle => rudder_common_report( @@ -121,7 +129,7 @@ bundle agent check_ssh_key_distribution "${sshkey_distribution_uuid[${sshkey_distribution_index}]}", "${component_name}", "${sshkey_distribution_tag[${sshkey_distribution_index}]}", "Wrong SSH key format \"${sshkey_distribution_tag[${sshkey_distribution_index}]}\" for user ${sshkey_distribution_name[${sshkey_distribution_index}]}" ), - ifvarclass => "!correct_ssh_key_format_${sshkey_distribution_index}.user_${sshkey_distribution_index}_exists"; + ifvarclass => "!correct_ssh_key_format_${sshkey_distribution_index}.ssh_key_distribution_user_${sshkey_distribution_name[${sshkey_distribution_index}]}_exists"; "No User Exist Report" usebundle => rudder_common_report( @@ -129,7 +137,7 @@ bundle agent check_ssh_key_distribution "${sshkey_distribution_uuid[${sshkey_distribution_index}]}", "${component_name}", "${sshkey_distribution_tag[${sshkey_distribution_index}]}", "The user ${sshkey_distribution_name[${sshkey_distribution_index}]} does NOT exist on this machine, not adding SSH key" ), - ifvarclass => "!user_${sshkey_distribution_index}_exists.correct_ssh_key_format_${sshkey_distribution_index}"; + ifvarclass => "!ssh_key_distribution_user_${sshkey_distribution_name[${sshkey_distribution_index}]}_exists.correct_ssh_key_format_${sshkey_distribution_index}"; "No User Exist and Wrong SSH Key Format Report" usebundle => rudder_common_report( @@ -137,7 +145,7 @@ bundle agent check_ssh_key_distribution "${sshkey_distribution_uuid[${sshkey_distribution_index}]}", "${component_name}", "${sshkey_distribution_tag[${sshkey_distribution_index}]}", "The user ${sshkey_distribution_name[${sshkey_distribution_index}]} does NOT exist on this machine, and the SSH key format is wrong" ), - ifvarclass => "!user_${sshkey_distribution_index}_exists.!correct_ssh_key_format_${sshkey_distribution_index}"; + ifvarclass => "!ssh_key_distribution_user_${sshkey_distribution_name[${sshkey_distribution_index}]}_exists.!correct_ssh_key_format_${sshkey_distribution_index}"; windows:: @@ -156,7 +164,7 @@ bundle agent check_ssh_key_distribution # - key value ends with "=" # - no spaces are allowed in options, except in double-quoted strings # -bundle edit_line append_or_replace_ssh_key(keyspec, key_content, index) +bundle edit_line append_or_replace_ssh_key_RudderUniqueID(keyspec, key_content, index) { vars: @@ -164,7 +172,7 @@ bundle edit_line append_or_replace_ssh_key(keyspec, key_content, index) "eline" comment => "An escaped version of the keyspec - \Q..\E do not escape everything", string => escape("${keyspec}"); - "key" string => escape("${key_content}"); + "key" string => escape("${key_content}"); insert_lines: @@ -172,13 +180,13 @@ bundle edit_line append_or_replace_ssh_key(keyspec, key_content, index) # NOTE: this is only to ensure that insert is attempted *after* the replace, # as normally insert step precedes the replace, see # (https://cfengine.com/docs/3.5/manuals-language-concepts-normal-ordering.html) - ifvarclass => canonify("ssh_key_distribution_replace_step_attempted_${index}"); + ifvarclass => canonify("ssh_key_distribution_replace_step_attempted_${index}_RudderUniqueID"); replace_patterns: - "^(?!${eline}$)(.*${key}.*)$" + "^(?!${eline}$)(.* ${key} .*)$" comment => "Replace a key here", replace_with => value("${keyspec}"), - classes => always("ssh_key_distribution_replace_step_attempted_${index}"); + classes => always("ssh_key_distribution_replace_step_attempted_${index}_RudderUniqueID"); }