summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwheatevo <18470637+wheatevo@users.noreply.github.com>2023-03-07 14:15:50 -0600
committerGitHub <noreply@github.com>2023-03-07 15:15:50 -0500
commitb73d00973326d882c712dd6d70f441f4660695d1 (patch)
tree93321da8f8e7afb29adb2be9aa5da10bcdef3901
parentf99e6335594d360defc97f734cd4cc5599b98c8a (diff)
downloadchef-b73d00973326d882c712dd6d70f441f4660695d1.tar.gz
Add selinux_user and selinux_login resources (#13511)
* Add `selinux_user` resource to manage SELinux users. * Add `selinux_login` resource to manage OS login to SELinux user mapping. Related to PR https://github.com/sous-chefs/selinux/pull/92 Signed-off-by: Matthew Newell <matthew.newell@oracle.com>
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/linux.rb25
-rw-r--r--lib/chef/resource/selinux_login.rb129
-rw-r--r--lib/chef/resource/selinux_user.rb137
-rw-r--r--lib/chef/resources.rb2
-rw-r--r--spec/unit/resource/selinux_login_spec.rb73
-rw-r--r--spec/unit/resource/selinux_user_spec.rb92
6 files changed, 458 insertions, 0 deletions
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb b/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
index d8e824fde6..b1b907a6d5 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
@@ -34,6 +34,31 @@ if platform_family?("rhel", "fedora", "amazon")
selinux_state "permissive" do
action :permissive
end
+
+ user "se_map_test"
+
+ selinux_user "se_map_test_u" do
+ level "s0"
+ range "s0"
+ roles %w{sysadm_r staff_r}
+ end
+
+ selinux_login "se_map_test" do
+ user "se_map_test_u"
+ range "s0"
+ end
+
+ selinux_login "se_map_test" do
+ action :delete
+ end
+
+ selinux_user "se_map_test_u" do
+ action :delete
+ end
+
+ user "se_map_test" do
+ action :remove
+ end
end
build_essential do
diff --git a/lib/chef/resource/selinux_login.rb b/lib/chef/resource/selinux_login.rb
new file mode 100644
index 0000000000..f634b2cb9c
--- /dev/null
+++ b/lib/chef/resource/selinux_login.rb
@@ -0,0 +1,129 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require_relative "../resource"
+require_relative "selinux/common_helpers"
+
+class Chef
+ class Resource
+ class SelinuxLogin < Chef::Resource
+ unified_mode true
+
+ provides :selinux_login
+
+ description "Use the **selinux_login** resource to add, update, or remove SELinux user to OS login mappings."
+ introduced "18.1"
+ examples <<~DOC
+ **Manage test OS user mapping with a range of s0 and associated SELinux user test_u**:
+
+ ```ruby
+ selinux_login 'test' do
+ user 'test_u'
+ range 's0'
+ end
+ ```
+ DOC
+
+ property :login, String,
+ name_property: true,
+ description: "An optional property to set the OS user login value if it differs from the resource block's name."
+
+ property :user, String,
+ description: "SELinux user to be mapped."
+
+ property :range, String,
+ description: "MLS/MCS security range for the SELinux user."
+
+ load_current_value do |new_resource|
+ logins = shell_out!("semanage login -l").stdout.split("\n")
+
+ current_login = logins.grep(/^#{Regexp.escape(new_resource.login)}\s+/) do |l|
+ l.match(/^(?<login>[^\s]+)\s+(?<user>[^\s]+)\s+(?<range>[^\s]+)/)
+ # match returns [<Match 'data'>] or [], shift converts that to <Match 'data'> or nil
+ end.shift
+
+ current_value_does_not_exist! unless current_login
+
+ # Existing resources should maintain their current configuration unless otherwise specified
+ new_resource.user ||= current_login[:user]
+ new_resource.range ||= current_login[:range]
+
+ user current_login[:user]
+ range current_login[:range]
+ end
+
+ action_class do
+ include Chef::SELinux::CommonHelpers
+
+ def semanage_login_args
+ # Generate arguments for semanage login -a or -m
+ args = ""
+
+ args += " -s #{new_resource.user}" if new_resource.user
+ args += " -r #{new_resource.range}" if new_resource.range
+
+ args
+ end
+ end
+
+ action :manage, description: "Sets the SELinux login mapping to the desired settings regardless of previous state." do
+ run_action(:add)
+ run_action(:modify)
+ end
+
+ # Create if doesn't exist, do not touch if user already exists
+ action :add, description: "Creates the SELinux login mapping if not previously created." do
+ raise "The user property must be populated to create a new SELinux login" if new_resource.user.to_s.empty?
+
+ if selinux_disabled?
+ Chef::Log.warn("Unable to add SELinux login #{new_resource.login} as SELinux is disabled")
+ return
+ end
+
+ unless current_resource
+ converge_if_changed do
+ shell_out!("semanage login -a#{semanage_login_args} #{new_resource.login}")
+ end
+ end
+ end
+
+ # Only modify port if it exists & doesn't have the correct context already
+ action :modify, description: "Updates the SELinux login mapping if previously created." do
+ if selinux_disabled?
+ Chef::Log.warn("Unable to modify SELinux login #{new_resource.login} as SELinux is disabled")
+ return
+ end
+
+ if current_resource
+ converge_if_changed do
+ shell_out!("semanage login -m#{semanage_login_args} #{new_resource.login}")
+ end
+ end
+ end
+
+ # Delete if exists
+ action :delete, description: "Removes the SELinux login mapping if previously created." do
+ if selinux_disabled?
+ Chef::Log.warn("Unable to delete SELinux login #{new_resource.login} as SELinux is disabled")
+ return
+ end
+
+ if current_resource
+ converge_by "deleting SELinux login #{new_resource.login}" do
+ shell_out!("semanage login -d #{new_resource.login}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/selinux_user.rb b/lib/chef/resource/selinux_user.rb
new file mode 100644
index 0000000000..ca8d69c919
--- /dev/null
+++ b/lib/chef/resource/selinux_user.rb
@@ -0,0 +1,137 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require_relative "../resource"
+require_relative "selinux/common_helpers"
+
+class Chef
+ class Resource
+ class SelinuxUser < Chef::Resource
+ unified_mode true
+
+ provides :selinux_user
+
+ description "Use the **selinux_user** resource to add, update, or remove SELinux users."
+ introduced "18.1"
+ examples <<~DOC
+ **Manage test_u SELinux user with a level and range of s0 and roles sysadm_r and staff_r**:
+
+ ```ruby
+ selinux_user 'test_u' do
+ level 's0'
+ range 's0'
+ roles %w(sysadm_r staff_r)
+ end
+ ```
+ DOC
+
+ property :user, String,
+ name_property: true,
+ description: "An optional property to set the SELinux user value if it differs from the resource block's name."
+
+ property :level, String,
+ description: "MLS/MCS security level for the SELinux user."
+
+ property :range, String,
+ description: "MLS/MCS security range for the SELinux user."
+
+ property :roles, Array,
+ description: "Associated SELinux roles for the user.",
+ coerce: proc { |r| Array(r).sort }
+
+ load_current_value do |new_resource|
+ users = shell_out!("semanage user -l").stdout.split("\n")
+
+ current_user = users.grep(/^#{Regexp.escape(new_resource.user)}\s+/) do |u|
+ u.match(/^(?<user>[^\s]+)\s+(?<prefix>[^\s]+)\s+(?<level>[^\s]+)\s+(?<range>[^\s]+)\s+(?<roles>.*)$/)
+ # match returns [<Match 'data'>] or [], shift converts that to <Match 'data'> or nil
+ end.shift
+
+ current_value_does_not_exist! unless current_user
+
+ # Existing resources should maintain their current configuration unless otherwise specified
+ new_resource.level ||= current_user[:level]
+ new_resource.range ||= current_user[:range]
+ new_resource.roles ||= current_user[:roles].to_s.split.sort
+
+ level current_user[:level]
+ range current_user[:range]
+ roles current_user[:roles].to_s.split.sort
+ end
+
+ action_class do
+ include Chef::SELinux::CommonHelpers
+
+ def semanage_user_args
+ # Generate arguments for semanage user -a or -m
+ args = ""
+
+ args += " -L #{new_resource.level}" if new_resource.level
+ args += " -r #{new_resource.range}" if new_resource.range
+ args += " -R '#{new_resource.roles.join(" ")}'" unless new_resource.roles.to_a.empty?
+
+ args
+ end
+ end
+
+ action :manage, description: "Sets the SELinux user to the desired settings regardless of previous state." do
+ run_action(:add)
+ run_action(:modify)
+ end
+
+ # Create if doesn't exist, do not touch if user already exists
+ action :add, description: "Creates the SELinux user if not previously created." do
+ raise "The roles property must be populated to create a new SELinux user" if new_resource.roles.to_a.empty?
+
+ if selinux_disabled?
+ Chef::Log.warn("Unable to add SELinux user #{new_resource.user} as SELinux is disabled")
+ return
+ end
+
+ unless current_resource
+ converge_if_changed do
+ shell_out!("semanage user -a#{semanage_user_args} #{new_resource.user}")
+ end
+ end
+ end
+
+ # Only modify port if it exists & doesn't have the correct context already
+ action :modify, description: "Updates the SELinux user if previously created." do
+ if selinux_disabled?
+ Chef::Log.warn("Unable to modify SELinux user #{new_resource.user} as SELinux is disabled")
+ return
+ end
+
+ if current_resource
+ converge_if_changed do
+ shell_out!("semanage user -m#{semanage_user_args} #{new_resource.user}")
+ end
+ end
+ end
+
+ # Delete if exists
+ action :delete, description: "Removes the SELinux user if previously created." do
+ if selinux_disabled?
+ Chef::Log.warn("Unable to delete SELinux user #{new_resource.user} as SELinux is disabled")
+ return
+ end
+
+ if current_resource
+ converge_by "deleting SELinux user #{new_resource.user}" do
+ shell_out!("semanage user -d #{new_resource.user}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 0d310f8bea..ca8e5f28c3 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -127,10 +127,12 @@ require_relative "resource/script"
require_relative "resource/selinux_boolean"
require_relative "resource/selinux_fcontext"
require_relative "resource/selinux_install"
+require_relative "resource/selinux_login"
require_relative "resource/selinux_module"
require_relative "resource/selinux_permissive"
require_relative "resource/selinux_port"
require_relative "resource/selinux_state"
+require_relative "resource/selinux_user"
require_relative "resource/service"
require_relative "resource/sudo"
require_relative "resource/sysctl"
diff --git a/spec/unit/resource/selinux_login_spec.rb b/spec/unit/resource/selinux_login_spec.rb
new file mode 100644
index 0000000000..42aeb52391
--- /dev/null
+++ b/spec/unit/resource/selinux_login_spec.rb
@@ -0,0 +1,73 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::SelinuxLogin do
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:resource) { Chef::Resource::SelinuxLogin.new("fakey_fakerton", run_context) }
+ let(:provider) { resource.provider_for_action(:manage) }
+
+ it "sets login property as name_property" do
+ expect(resource.login).to eql("fakey_fakerton")
+ end
+
+ it "sets the default action as :manage" do
+ expect(resource.action).to eql([:manage])
+ end
+
+ it "supports :manage, :add, :modify, :delete actions" do
+ expect { resource.action :manage }.not_to raise_error
+ expect { resource.action :add }.not_to raise_error
+ expect { resource.action :modify }.not_to raise_error
+ expect { resource.action :delete }.not_to raise_error
+ end
+
+ describe "#semanage_login_args" do
+ let(:provider) { resource.provider_for_action(:modify) }
+
+ context "when no parameters are provided" do
+ it "returns an empty string" do
+ expect(provider.semanage_login_args).to eq("")
+ end
+ end
+
+ context "when all parameters are provided" do
+ it "returns all params" do
+ resource.user "user_u"
+ resource.range "s0"
+ expect(provider.semanage_login_args).to eq(" -s user_u -r s0")
+ end
+ end
+
+ context "when no user is provided" do
+ it "returns range param" do
+ resource.range "s0"
+ expect(provider.semanage_login_args).to eq(" -r s0")
+ end
+ end
+
+ context "when no range is provided" do
+ it "returns user param" do
+ resource.user "user_u"
+ expect(provider.semanage_login_args).to eq(" -s user_u")
+ end
+ end
+ end
+end
diff --git a/spec/unit/resource/selinux_user_spec.rb b/spec/unit/resource/selinux_user_spec.rb
new file mode 100644
index 0000000000..227b79d8b9
--- /dev/null
+++ b/spec/unit/resource/selinux_user_spec.rb
@@ -0,0 +1,92 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::SelinuxUser do
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:resource) { Chef::Resource::SelinuxUser.new("fakey_fakerton", run_context) }
+ let(:provider) { resource.provider_for_action(:manage) }
+ let(:semanage_list) { double("shellout", stdout: "") }
+
+ it "sets user property as name_property" do
+ expect(resource.user).to eql("fakey_fakerton")
+ end
+
+ it "sets the default action as :manage" do
+ expect(resource.action).to eql([:manage])
+ end
+
+ it "supports :manage, :add, :modify, :delete actions" do
+ expect { resource.action :manage }.not_to raise_error
+ expect { resource.action :add }.not_to raise_error
+ expect { resource.action :modify }.not_to raise_error
+ expect { resource.action :delete }.not_to raise_error
+ end
+
+ it "sorts roles property values" do
+ expect { resource.roles %w{c a b} }.not_to raise_error
+ expect(resource.roles).to eq(%w{a b c})
+ end
+
+ describe "#semanage_user_args" do
+ let(:provider) { resource.provider_for_action(:modify) }
+
+ context "when no parameters are provided" do
+ it "returns an empty string" do
+ expect(provider.semanage_user_args).to eq("")
+ end
+ end
+
+ context "when all parameters are provided" do
+ it "returns all params" do
+ resource.level "s0"
+ resource.range "s0"
+ resource.roles %w{sysadm_r staff_r}
+ expect(provider.semanage_user_args).to eq(" -L s0 -r s0 -R 'staff_r sysadm_r'")
+ end
+ end
+
+ context "when no roles are provided" do
+ it "returns level and range params" do
+ resource.level "s0"
+ resource.range "s0"
+ resource.roles []
+
+ expect(provider.semanage_user_args).to eq(" -L s0 -r s0")
+ end
+ end
+
+ context "when no range is provided" do
+ it "returns level and roles params" do
+ resource.level "s0"
+ resource.roles %w{sysadm_r staff_r}
+ expect(provider.semanage_user_args).to eq(" -L s0 -R 'staff_r sysadm_r'")
+ end
+ end
+
+ context "when no level is provided" do
+ it "returns range and roles params" do
+ resource.range "s0"
+ resource.roles %w{sysadm_r staff_r}
+ expect(provider.semanage_user_args).to eq(" -r s0 -R 'staff_r sysadm_r'")
+ end
+ end
+ end
+end