Skip to content

Commit

Permalink
Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
ncharles committed Aug 24, 2018
1 parent 2985042 commit 9c5a0d6
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 14 deletions.
167 changes: 167 additions & 0 deletions techniques/system/common/1.0/hooks.st
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,170 @@ 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_<username>_to_flush , and check if user is present
# with class ssh_key_distribution_user_<username>_exists
# It also computes, for each user,
# - the homedir homedir[<username>]
# - the gid gid[<username>]
# And create empty temp key files in /var/rudder/tmp/check_ssh_key_distribution/
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
"bundles" slist => bundlesmatching("default:check_ssh_key_distribution_.*", "check_ssh_key_distribution_technique");

# get all indices for each of the bundle
"array[${bundles}]" slist => getindices("${bundles}.sshkey_distribution_name");


# Path to the temporary files created for the checking the files
"temp_ssh_key_path" string => "/var/rudder/tmp/check_ssh_key_distribution/";

pass1::
# get the list of all users to manage in list userlist
"raw_userlist" slist => classesmatching("userlist_.*", "sshKeyDistribution_userlist");
"userlist" slist => maplist(regex_replace("${this}", "userlist_", "", ""), "@{raw_userlist}");


# 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:
# to get the list of all users, only found way was to define a class for each user, with a tag 'sshKeyDistribution_userlist', and explore this list.
"userlist_${${bundles}.sshkey_distribution_name[${array[${bundles}]}]}" expression => "any",
meta => { "sshKeyDistribution_userlist" };

# define the class "ssh_key_distribution_<username>_to_flush" in 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";

# 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");

pass2::
# check if user is present on the system
"ssh_key_distribution_user_${userlist}_exists" expression => userexists("${userlist}"),
scope => "namespace";

any::
"pass2" expression => "pass1";
"pass1" expression => "any";


files:
pass2::
"${temp_ssh_key_path}/${userlist}.authorized_keys.tmp"
create => "true",
edit_line => insert_lines(""),
edit_defaults => empty,
ifvarclass => "ssh_key_distribution_user_${userlist}_exists";

}


# Compare the ssh key file with the temporary one
# We also need to know if any of the user is in enforce
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[${sshkey_distribution_name[${sshkey_distribution_index}]}]}/.ssh/${config_basename}",
ifvarclass => "ssh_key_distribution_user_${runhook_sshKeyDistribution_pre_hook.userlist}_exists";


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 & 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");


any::
"pass2" expression => "pass1";
"pass1" expression => "any";


files:
# replace file if size if same_keys_different_size_ssh_file_for_<user> or ssh_keys_repaired_<user> or same_size_but_repaired_keys_<user> and ssh_key_distribution_<username>_to_flush, and in ENFORCE (we need to know when enforce)
# do we also need to have the report id ? maybe ...
"${runhook_sshKeyDistribution_pre_hook.homedir[${runhook_sshKeyDistribution_pre_hook.userlist}]}/.ssh/${config_basename}"
copy_from => copy_digest("${runhook_sshKeyDistribution_pre_hook.temp_ssh_key_path}/${runhook_sshKeyDistribution_pre_hook.userlist}.authorized_keys.tmp"),
classes => classes_generic("FIND A NAME<user>"),
ifvarclass => "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})";


}
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<OS version=">= 4 (Nahant)">RHEL / CentOS</OS>
<OS version=">= 10 SP1 (Agama Lizard)">SuSE LES / DES / OpenSuSE</OS>
<OS version=">= 5.3">AIX</OS>
<AGENT version=">= 3.4.4">cfengine-community</AGENT>
<AGENT version=">= 3.7">cfengine-community</AGENT>
</COMPATIBLE>

<MULTIINSTANCE>true</MULTIINSTANCE>
<POLICYGENERATION>separated</POLICYGENERATION>

<BUNDLES>
<NAME>check_ssh_key_distribution</NAME>
<NAME>check_ssh_key_distribution_RudderUniqueID</NAME>
</BUNDLES>

<TMLS>
<TML name="sshKeyDistribution"/>
</TMLS>

<RUNHOOKS>
<PRE bundle="runhook_sshKeyDistribution_pre_hook">
<REPORT name="PLACEHOLDER"/>
</PRE>
<POST bundle="runhook_sshKeyDistribution_post_hook">
<REPORT name="PLACEHOLDER"/>
</POST>
</RUNHOOKS>

<!-- Policy Instance Settings -->
<TRACKINGVARIABLE>
<SAMESIZEAS>SSH_KEY_DISTRIBUTION_NAME</SAMESIZEAS>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,26 @@
#
#####################################################################################

# 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_<username>_to_flush - if set, we asked to flush the userkey
# var: runhook_sshKeyDistribution_pre_hook.userlist list of user set

# 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
{
meta:
"tags" slist => { "check_ssh_key_distribution_technique" };
vars:
any::
Expand All @@ -43,14 +58,14 @@ bundle agent check_ssh_key_distribution
"sshkey_distribution_index"
slist => getindices("sshkey_distribution_name");
"no_${sshkey_distribution_index}"
int => parsestringarray("userarray_${sshkey_distribution_index}", "${userdata_${sshkey_distribution_index}}", "", ":", "1000", "200000" );
# #"no_${sshkey_distribution_index}"
# # int => parsestringarray("userarray_${sshkey_distribution_index}", "${userdata_${sshkey_distribution_index}}", "", ":", "1000", "200000" );
"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]}";
# "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";
Expand All @@ -73,8 +88,8 @@ bundle agent check_ssh_key_distribution
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]}";
# "gid[${sshkey_distribution_index}]"
# string => "${userarray_${sshkey_distribution_index}[${sshkey_distribution_name[${sshkey_distribution_index}]}][3]}";
classes:
"pass2" expression => "pass1";
Expand All @@ -90,19 +105,29 @@ bundle agent check_ssh_key_distribution
!windows::
"${homedir[${sshkey_distribution_index}]}/.ssh/."
"${runhook_sshKeyDistribution_pre_hook.homedir[${sshkey_distribution_name[${sshkey_distribution_index}]}]}/.ssh/."
create => "true",
perms => mog("700", "${sshkey_distribution_name[${sshkey_distribution_index}]}", "${gid[${sshkey_distribution_index}]}"),
perms => mog("700", "${sshkey_distribution_name[${sshkey_distribution_index}]}", "${runhook_sshKeyDistribution_pre_hook.gid[${sshkey_distribution_name[${sshkey_distribution_index}]}]}"),
ifvarclass => concat(canonify("user_${sshkey_distribution_index}_exists"),".correct_ssh_key_format_${sshkey_distribution_index}");
"${homedir[${sshkey_distribution_index}]}/.ssh/${config_basename}"
# we should not empty select anymore
"${runhook_sshKeyDistribution_pre_hook.homedir[${sshkey_distribution_name[${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}]}"),
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("${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}]}", "check_ssh_key_distribution_user_key_${sshkey_distribution_name[${sshkey_distribution_index}]}"),
ifvarclass => concat(canonify("user_${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("${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:
pass2.!windows::
Expand Down

0 comments on commit 9c5a0d6

Please sign in to comment.