From 0838c10b3088ae46e082f37c496d02807c200015 Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Mon, 18 Jul 2022 15:56:30 -0400 Subject: [PATCH 01/10] add support for selinux login --- examples/login.pp | 14 ++ lib/puppet/provider/selinux_login/semanage.rb | 123 ++++++++++++++++++ lib/puppet/type/selinux_login.rb | 30 +++++ .../voxpupuli/selinux/semanage_users.py | 37 ++++++ manifests/init.pp | 4 + manifests/login.pp | 44 +++++++ 6 files changed, 252 insertions(+) create mode 100644 examples/login.pp create mode 100644 lib/puppet/provider/selinux_login/semanage.rb create mode 100644 lib/puppet/type/selinux_login.rb create mode 100644 lib/puppet_x/voxpupuli/selinux/semanage_users.py create mode 100644 manifests/login.pp diff --git a/examples/login.pp b/examples/login.pp new file mode 100644 index 00000000..b13d3282 --- /dev/null +++ b/examples/login.pp @@ -0,0 +1,14 @@ + +# Groups +selinux::login { '%cn_cegbu_aconex_fr-dev-ops-priv_staff_u': + ensure => 'present', + selinux_login_name => '%cn_cegbu_aconex_fr-dev-ops-priv', + selinux_user => 'staff_u' +} + +# Individual +selinux::login { 'localuser_staff_u': + ensure => 'present', + selinux_login_name => 'localuser', + selinux_user => 'staff_u' +} diff --git a/lib/puppet/provider/selinux_login/semanage.rb b/lib/puppet/provider/selinux_login/semanage.rb new file mode 100644 index 00000000..e1a91a05 --- /dev/null +++ b/lib/puppet/provider/selinux_login/semanage.rb @@ -0,0 +1,123 @@ +Puppet::Type.type(:selinux_login).provide(:semanage) do + desc 'Support managing SELinux login definitions via semanage' + + defaultfor kernel: 'Linux' + # semanage fails when SELinux is disabled, so let's not pretend to work in that situation. + confine selinux: true + + osfamily = Facter.value('osfamily') + osversion = Facter.value('operatingsystemmajrelease') + operatingsystem = Facter.value('operatingsystem') + + # Determine the appropriate python command + def self.python_command + @python_command ||= nil + return @python_command if @python_command + + # Find the correct version of python on the system + python_paths = [ + '/usr/libexec/platform-python', + 'python', + 'python3', + 'python2' + ] + + valid_paths = [] + + python_paths.each do |pypath| + candidate = Puppet::Util.which(pypath) + + next unless candidate + valid_paths << candidate + + if Puppet::Util::Execution.execute("#{candidate} -c 'import semanage'", failonfail: false).exitstatus.zero? + @python_command = candidate + break + end + end + + return @python_command if @python_command + + # Since this is used in 'instances', we have to shrug and hope for the + # best unless we want runs to fail until the system is 100% correct. + # So far, it does not appear to hurt anything in practice and preserves the + # behavior from previous releases that hard coded the path into the python + # script. + valid_paths.first + end + + # current file path is lib/puppet/provider/selinux_login/semanage.rb + # semanage_users.py is lib/puppet_x/voxpupuli/selinux/semanage_users.py + USERS_HELPER = File.expand_path('../../../../puppet_x/voxpupuli/selinux/semanage_users.py', __FILE__) + commands semanage: 'semanage', + python: python_command + + mk_resource_methods + + def self.parse_helper_lines(lines) + ret = {} + lines.each do |line| + split = line.split(%r{\s+}) + # helper format is: + # root unconfined_u + # system_u system_u + # __default__ unconfined_u + # %cn_cegbu_aconex_fr-dev-ops-priv unconfined_u + # %cn_cegbu_aconex_fr-dev-platform-priv unconfined_u + selinux_login_name, selinux_user = split + + key = "#{selinux_login_name}" + ret[key] = { + ensure: :present, + name: key, + selinux_login_name: selinux_login_name, + selinux_user: selinux_user + } + end + ret + end + + def self.instances + # no way to do this with one call as far as I know + policy = parse_helper_lines(python(USERS_HELPER).split("\n")) + policy.values.map do |item| + new(item) + end + end + + def self.prefetch(resources) + # is there a better way to do this? map port/protocol pairs to the provider regardless of the title + # and make sure all system resources have ensure => :present so that we don't try to remove them + instances.each do |provider| + resource = resources[provider.name] + if resource + unless resource[:selinux_user].to_s == provider.selinux_user && resource[:selinux_login_name].to_s == provider.selinux_login_name || resource.purging? + raise Puppet::ResourceError, "Selinux_port['#{resource[:name]}']: title does not match its port and protocol, and a conflicting resource exists" + end + resource.provider = provider + resource[:ensure] = :present if provider.source == :policy + else + resources.values.each do |res| + next unless res[:selinux_user] == provider.selinux_user && res[:selinux_login_name] == provider.selinux_login_name + warning("Selinux_login['#{resource[:name]}']: title does not match format selinux_login_name_selinux_user") + resource.provider = provider + resource[:ensure] = :present if provider.source == :policy + end + end + end + end + + def create + args = ['login', '-a', '-s', @resource[:selinux_user], @resource[:selinux_login_name]] + semanage(*args) + end + + def destroy + args = ['login', '-d', @property_hash[:selinux_login_name]] + semanage(*args) + end + + def exists? + @property_hash[:ensure] == :present + end +end diff --git a/lib/puppet/type/selinux_login.rb b/lib/puppet/type/selinux_login.rb new file mode 100644 index 00000000..03720668 --- /dev/null +++ b/lib/puppet/type/selinux_login.rb @@ -0,0 +1,30 @@ +Puppet::Type.newtype(:selinux_login) do + @doc = 'Manage SELinux login definitions. You should use selinux::login instead of this directly.' + + ensurable + + newparam(:title, namevar: true) do + desc 'Should be of the form "linuxuser_selinuxuser" or the type may misbehave' + end + + newproperty(:selinux_login_name) do + desc 'The name of the linux user or group to map.' + isrequired + end + + newproperty(:selinux_user) do + desc 'The selinux user to map to.' + isrequired + end + + autorequire(:package) do + [ + 'policycoreutils', + 'policycoreutils-python', + 'policycoreutils-python-utils', + 'python3-policycoreutils', + 'selinux-policy-dev', + 'selinux-policy-devel' + ] + end +end diff --git a/lib/puppet_x/voxpupuli/selinux/semanage_users.py b/lib/puppet_x/voxpupuli/selinux/semanage_users.py new file mode 100644 index 00000000..28b036dc --- /dev/null +++ b/lib/puppet_x/voxpupuli/selinux/semanage_users.py @@ -0,0 +1,37 @@ +# This script uses libsemanage directly to access the ports list +# it is *much* faster than semanage port -l + +# will work with python 2.6+ +from __future__ import print_function +from sys import exit +try: + import semanage +except ImportError: + # The semanage python library does not exist, so let's assume SELinux is disabled... + # In this case, the correct response is to return no ports when puppet does a + # prefetch, to avoid an error. We depend on the semanage binary anyway, which + # is uses the library + exit(0) + + +handle = semanage.semanage_handle_create() + +if semanage.semanage_is_managed(handle) < 0: + exit(1) +if semanage.semanage_connect(handle) < 0: + exit(1) + +def print_seuser(seuser): + seuser_login = semanage.semanage_seuser_get_name(seuser) + selinux_user = semanage.semanage_seuser_get_sename(seuser) + print("{} {}".format(seuser_login, selinux_user)) + + +(status, seusers) = semanage.semanage_seuser_list(handle) + +for seuser in seusers: + print_seuser(seuser) + + +semanage.semanage_disconnect(handle) +semanage.semanage_handle_destroy(handle) diff --git a/manifests/init.pp b/manifests/init.pp index 62cfdb2a..577bc050 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -54,6 +54,7 @@ Optional[Hash] $permissive = undef, Optional[Hash] $port = undef, Optional[Hash] $exec_restorecon = undef, + Optional[Hash] $login = undef, ) { class { 'selinux::package': manage_package => $manage_package, @@ -89,6 +90,9 @@ if $exec_restorecon { create_resources ( 'selinux::exec_restorecon', $exec_restorecon ) } + if $login { + create_resources ( 'selinux::login', $login) + } # Ordering anchor { 'selinux::start': } diff --git a/manifests/login.pp b/manifests/login.pp new file mode 100644 index 00000000..c8579342 --- /dev/null +++ b/manifests/login.pp @@ -0,0 +1,44 @@ +# This method will manage a selinux login, and will +# persist it across reboots. +# +# @summary Manage a SELinux login +# +# @example Add a map for the localuser to staff_u +# selinux::login { 'localuser_staff_u': +# ensure => 'present', +# selinux_login_name => 'localuser', +# selinux_user => 'staff_u', +# } +# +# @param ensure Set to present to add or absent to remove a selinux login. +# @param selinux_login_name A Linux user or group +# @param selinux_user The selinux user to map to +# +define selinux::login ( + String $selinux_login_name, + String $selinux_user, + Enum['present', 'absent'] $ensure = 'present', +) { + include selinux + + if $ensure == 'present' { + Anchor['selinux::module post'] + -> Selinux::Login[$title] + -> Anchor['selinux::end'] + } elsif $ensure == 'absent' { + Class['selinux::config'] + -> Selinux::Login[$title] + -> Anchor['selinux::module pre'] + } else { + fail('Unexpected $ensure value') + } + + # Do nothing unless SELinux is enabled + if $facts['os']['selinux']['enabled'] { + selinux_login { "${selinux_login_name}_${selinux_user}": + ensure => $ensure, + selinux_login_name => $selinux_login_name, + selinux_user => $selinux_user, + } + } +} From 7ffc10006b01e22acf49479945e2c67097e7768a Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Mon, 18 Jul 2022 16:06:05 -0400 Subject: [PATCH 02/10] cleanup linting --- examples/login.pp | 14 +++++++------- manifests/login.pp | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/login.pp b/examples/login.pp index b13d3282..d336c32f 100644 --- a/examples/login.pp +++ b/examples/login.pp @@ -1,14 +1,14 @@ - + # Groups selinux::login { '%cn_cegbu_aconex_fr-dev-ops-priv_staff_u': - ensure => 'present', - selinux_login_name => '%cn_cegbu_aconex_fr-dev-ops-priv', - selinux_user => 'staff_u' + ensure => 'present', + selinux_login_name => '%cn_cegbu_aconex_fr-dev-ops-priv', + selinux_user => 'staff_u', } # Individual selinux::login { 'localuser_staff_u': - ensure => 'present', - selinux_login_name => 'localuser', - selinux_user => 'staff_u' + ensure => 'present', + selinux_login_name => 'localuser', + selinux_user => 'staff_u', } diff --git a/manifests/login.pp b/manifests/login.pp index c8579342..ba53c1a6 100644 --- a/manifests/login.pp +++ b/manifests/login.pp @@ -36,9 +36,9 @@ # Do nothing unless SELinux is enabled if $facts['os']['selinux']['enabled'] { selinux_login { "${selinux_login_name}_${selinux_user}": - ensure => $ensure, - selinux_login_name => $selinux_login_name, - selinux_user => $selinux_user, + ensure => $ensure, + selinux_login_name => $selinux_login_name, + selinux_user => $selinux_user, } } } From 0007a267bded41d7e277c95e74588c2720c50f76 Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Mon, 18 Jul 2022 16:07:22 -0400 Subject: [PATCH 03/10] documented param for login --- manifests/init.pp | 1 + 1 file changed, 1 insertion(+) diff --git a/manifests/init.pp b/manifests/init.pp index 577bc050..1e9c1847 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -31,6 +31,7 @@ # @param permissive Hash of selinux::module resource parameters # @param port Hash of selinux::port resource parameters # @param exec_restorecon Hash of selinux::exec_restorecon resource parameters +# @param login Hash of selinux::login resource parameters # class selinux ( Variant[String[1], Array[String[1]]] $package_name, From 36dc9265f61b062abf54f2ba7ee8276dd019d7b1 Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Mon, 18 Jul 2022 16:09:34 -0400 Subject: [PATCH 04/10] remove empty line --- examples/login.pp | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/login.pp b/examples/login.pp index d336c32f..6703bb61 100644 --- a/examples/login.pp +++ b/examples/login.pp @@ -1,4 +1,3 @@ - # Groups selinux::login { '%cn_cegbu_aconex_fr-dev-ops-priv_staff_u': ensure => 'present', From e759547d8a51c4a09b1ffc2152803331aaf994e7 Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Mon, 18 Jul 2022 16:11:46 -0400 Subject: [PATCH 05/10] remove useless vars --- lib/puppet/provider/selinux_login/semanage.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/puppet/provider/selinux_login/semanage.rb b/lib/puppet/provider/selinux_login/semanage.rb index e1a91a05..89a83890 100644 --- a/lib/puppet/provider/selinux_login/semanage.rb +++ b/lib/puppet/provider/selinux_login/semanage.rb @@ -5,10 +5,6 @@ # semanage fails when SELinux is disabled, so let's not pretend to work in that situation. confine selinux: true - osfamily = Facter.value('osfamily') - osversion = Facter.value('operatingsystemmajrelease') - operatingsystem = Facter.value('operatingsystem') - # Determine the appropriate python command def self.python_command @python_command ||= nil From 4ba390a8a5e36497267bc8a14fe2742dc5292df0 Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Mon, 18 Jul 2022 16:13:19 -0400 Subject: [PATCH 06/10] use to_s --- lib/puppet/provider/selinux_login/semanage.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/provider/selinux_login/semanage.rb b/lib/puppet/provider/selinux_login/semanage.rb index 89a83890..7eafb2b1 100644 --- a/lib/puppet/provider/selinux_login/semanage.rb +++ b/lib/puppet/provider/selinux_login/semanage.rb @@ -62,7 +62,7 @@ def self.parse_helper_lines(lines) # %cn_cegbu_aconex_fr-dev-platform-priv unconfined_u selinux_login_name, selinux_user = split - key = "#{selinux_login_name}" + key = selinux_login_name.to_s ret[key] = { ensure: :present, name: key, From d021cfe2ae7c6ce21a533c883cdc8625c42fc8b4 Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Tue, 19 Jul 2022 14:32:02 -0400 Subject: [PATCH 07/10] add in fixes for PR review --- manifests/init.pp | 6 +++++- manifests/login.pp | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/manifests/init.pp b/manifests/init.pp index 1e9c1847..62bf8ef2 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -92,7 +92,11 @@ create_resources ( 'selinux::exec_restorecon', $exec_restorecon ) } if $login { - create_resources ( 'selinux::login', $login) + $login.each |$login_name, $login_attributes { + selinux::login { $login_name: + * => $login_attributes, + } +} } # Ordering diff --git a/manifests/login.pp b/manifests/login.pp index ba53c1a6..fd844bdd 100644 --- a/manifests/login.pp +++ b/manifests/login.pp @@ -15,8 +15,8 @@ # @param selinux_user The selinux user to map to # define selinux::login ( - String $selinux_login_name, - String $selinux_user, + String[1] $selinux_login_name, + String[1] $selinux_user, Enum['present', 'absent'] $ensure = 'present', ) { include selinux From c36f85cde243d8809ea2a2c1461526d262fdf5de Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Tue, 19 Jul 2022 14:34:27 -0400 Subject: [PATCH 08/10] add | --- manifests/init.pp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/init.pp b/manifests/init.pp index 62bf8ef2..d5a1da26 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -92,7 +92,7 @@ create_resources ( 'selinux::exec_restorecon', $exec_restorecon ) } if $login { - $login.each |$login_name, $login_attributes { + $login.each |$login_name, $login_attributes| { selinux::login { $login_name: * => $login_attributes, } From 9cbd406476bee72078ba90fedf2fec5610c93e14 Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Fri, 22 Jul 2022 22:06:55 -0400 Subject: [PATCH 09/10] Update manifests/init.pp Co-authored-by: Tim Meusel --- manifests/init.pp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/init.pp b/manifests/init.pp index d5a1da26..acd53c27 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -55,7 +55,7 @@ Optional[Hash] $permissive = undef, Optional[Hash] $port = undef, Optional[Hash] $exec_restorecon = undef, - Optional[Hash] $login = undef, + Hash[String[1],Hash[String[1],String[1]]] $login = {}, ) { class { 'selinux::package': manage_package => $manage_package, From 0578fcffbb8af050733a6dbf18e4e55bb8ddf6d6 Mon Sep 17 00:00:00 2001 From: Brock Shelton Date: Fri, 22 Jul 2022 22:09:17 -0400 Subject: [PATCH 10/10] drop if statement --- manifests/init.pp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/manifests/init.pp b/manifests/init.pp index acd53c27..6bc169af 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -91,12 +91,10 @@ if $exec_restorecon { create_resources ( 'selinux::exec_restorecon', $exec_restorecon ) } - if $login { - $login.each |$login_name, $login_attributes| { - selinux::login { $login_name: + $login.each |$login_name, $login_attributes| { + selinux::login { $login_name: * => $login_attributes, } -} } # Ordering