diff options
author | neha-p6 <neha.pansare@progress.com> | 2022-04-11 11:00:57 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-11 11:00:57 +0530 |
commit | d3dda786b2a6d61025288c002ae7b5b60c499f92 (patch) | |
tree | 2e48a0e853c05ca404be357ba2c928d491ec7b01 /lib | |
parent | d3e922a67abec71825d6f6c6ac8fd4ef9fc53019 (diff) | |
download | chef-d3dda786b2a6d61025288c002ae7b5b60c499f92.tar.gz |
SELinux integration to infra client (#12694)
* 1. Add resources for SELlinux
2. Add common helper for SELinux under a new subdirectory
3. Wire files together with corresponding changes
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 3. Include SElinux CommonHelper under action_class for corresponding resources as it uses shell_out!
4. Add SELinux config file templates for debian and default versions
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 5.Add local mode true to correctly parse template from selinux_state resource
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 6. Remove SELinux cookbook dependency from kitchen-tests as SELinux resources are now part of core chef client, update linux.rb recipe to use corresponding SELinux resources instead of include_recipe
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 7. Add unit test cases for SELinux resources
8. Add documentation for SELinux resources
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 9. Obvious fix: code linting and spellcheck
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 10. Add code linting changes.
11. Add missing comma in cspell.json resulting in issue
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 12. Add linting and spellcheck changes
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 13. Add documentation for SELinux resources for all properties, actions with examples
14. Added permissive SELinux policy for en_to_end kitchen test
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
* 15. Fix chefstyle linting
16. Update few shell_out calls to use array format of input parameters
Signed-off-by: Neha Pansare <neha.pansare@progress.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chef/resource/selinux/common_helpers.rb | 47 | ||||
-rw-r--r-- | lib/chef/resource/selinux/selinux_debian.erb | 18 | ||||
-rw-r--r-- | lib/chef/resource/selinux/selinux_default.erb | 15 | ||||
-rw-r--r-- | lib/chef/resource/selinux_boolean.rb | 101 | ||||
-rw-r--r-- | lib/chef/resource/selinux_fcontext.rb | 160 | ||||
-rw-r--r-- | lib/chef/resource/selinux_install.rb | 107 | ||||
-rw-r--r-- | lib/chef/resource/selinux_module.rb | 143 | ||||
-rw-r--r-- | lib/chef/resource/selinux_permissive.rb | 64 | ||||
-rw-r--r-- | lib/chef/resource/selinux_port.rb | 118 | ||||
-rw-r--r-- | lib/chef/resource/selinux_state.rb | 166 | ||||
-rw-r--r-- | lib/chef/resources.rb | 7 |
11 files changed, 946 insertions, 0 deletions
diff --git a/lib/chef/resource/selinux/common_helpers.rb b/lib/chef/resource/selinux/common_helpers.rb new file mode 100644 index 0000000000..a67943abad --- /dev/null +++ b/lib/chef/resource/selinux/common_helpers.rb @@ -0,0 +1,47 @@ +# +# 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. +# +class Chef + module SELinux + module CommonHelpers + def selinux_disabled? + selinux_state.eql?(:disabled) + end + + def selinux_enforcing? + selinux_state.eql?(:enforcing) + end + + def selinux_permissive? + selinux_state.eql?(:permissive) + end + + def state_change_reboot_required? + (selinux_disabled? && %i{enforcing permissive}.include?(action)) || ((selinux_enforcing? || selinux_permissive?) && action == :disabled) + end + + def selinux_state + state = shell_out!("getenforce").stdout.strip.downcase.to_sym + raise "Got unknown SELinux state #{state}" unless %i{disabled enforcing permissive}.include?(state) + + state + end + + def selinux_activate_required? + return false unless platform_family?("debian") + + !File.read("/etc/default/grub").match?("security=selinux") + end + end + end +end diff --git a/lib/chef/resource/selinux/selinux_debian.erb b/lib/chef/resource/selinux/selinux_debian.erb new file mode 100644 index 0000000000..07a11a0239 --- /dev/null +++ b/lib/chef/resource/selinux/selinux_debian.erb @@ -0,0 +1,18 @@ +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand. +# + +# This file controls the state of SELinux on the system. +# SELINUX= can take one of these three values: +# enforcing - SELinux security policy is enforced. +# permissive - SELinux prints warnings instead of enforcing. +# disabled - No SELinux policy is loaded. +SELINUX=<%= @selinux %> +# SELINUXTYPE= can take one of these three values: +# default - equivalent to the old strict and targeted policies +# mls - Multi-Level Security (for military and educational use) +# src - Custom policy built from source +SELINUXTYPE=<%= @selinuxtype %> + +# SETLOCALDEFS= Check local definition changes +SETLOCALDEFS=0
\ No newline at end of file diff --git a/lib/chef/resource/selinux/selinux_default.erb b/lib/chef/resource/selinux/selinux_default.erb new file mode 100644 index 0000000000..0fec407493 --- /dev/null +++ b/lib/chef/resource/selinux/selinux_default.erb @@ -0,0 +1,15 @@ +# Generated by Chef for <%= node['fqdn'] %> +# Do NOT modify this file by hand. +# + +# This file controls the state of SELinux on the system. +# SELINUX= can take one of these three values: +# enforcing - SELinux security policy is enforced. +# permissive - SELinux prints warnings instead of enforcing. +# disabled - No SELinux policy is loaded. +SELINUX=<%= @selinux %> +# SELINUXTYPE= can take one of these three values: +# targeted - Targeted processes are protected, +# minimum - Modification of targeted policy. Only selected processes are protected. +# mls - Multi Level Security protection. +SELINUXTYPE=<%= @selinuxtype %>
\ No newline at end of file diff --git a/lib/chef/resource/selinux_boolean.rb b/lib/chef/resource/selinux_boolean.rb new file mode 100644 index 0000000000..919c1d274a --- /dev/null +++ b/lib/chef/resource/selinux_boolean.rb @@ -0,0 +1,101 @@ +# +# 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 SelinuxBoolean < Chef::Resource + unified_mode true + + provides :selinux_boolean + + description "Use **selinux_boolean** resource to set SELinux boolean values." + introduced "18.0" + examples <<~DOC + **Set ssh_keysign to true**: + + ```ruby + selinux_boolean 'ssh_keysign' do + value true + end + ``` + + **Set ssh_sysadm_login to 'on'**: + + ```ruby + selinux_boolean 'ssh_sysadm_login' do + value 'on' + end + ``` + DOC + + property :boolean, String, + name_property: true, + description: "SELinux boolean to set." + + property :value, [Integer, String, true, false], + required: true, + equal_to: %w{on off}, + coerce: proc { |p| selinux_bool(p) }, + description: "SELinux boolean value." + + property :persistent, [true, false], + default: true, + desired_state: false, + description: "Set to true for value setting to survive reboot." + + load_current_value do |new_resource| + value shell_out!("getsebool", new_resource.boolean).stdout.split("-->").map(&:strip).last + end + + action_class do + include Chef::SELinux::CommonHelpers + end + + action :set , description: "Set the state of the boolean." do + if selinux_disabled? + Chef::Log.warn("Unable to set SELinux boolean #{new_resource.name} as SELinux is disabled") + return + end + + converge_if_changed do + cmd = "setsebool" + cmd += " -P" if new_resource.persistent + cmd += " #{new_resource.boolean} #{new_resource.value}" + + shell_out!(cmd) + end + end + + private + + # + # Validate and return input boolean value in required format + # @param bool [String, Integer, Boolean] Input boolean value in allowed formats + # + # @return [String] [description] Boolean value in required format + def selinux_bool(bool) + if ["on", "true", "1", true, 1].include?(bool) + "on" + elsif ["off", "false", "0", false, 0].include?(bool) + "off" + else + raise ArgumentError, "selinux_bool: Invalid selinux boolean value #{bool}" + end + end + end + end +end diff --git a/lib/chef/resource/selinux_fcontext.rb b/lib/chef/resource/selinux_fcontext.rb new file mode 100644 index 0000000000..243b180f3c --- /dev/null +++ b/lib/chef/resource/selinux_fcontext.rb @@ -0,0 +1,160 @@ +# +# 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 SelinuxFcontext < Chef::Resource + unified_mode true + + provides :selinux_fcontext + + description "Use **selinux_fcontext** resource to set the SELinux context of files with semanage fcontext." + introduced "18.0" + examples <<~DOC + **Allow http servers (e.g. nginx/apache) to modify moodle files**: + + ```ruby + selinux_fcontext '/var/www/moodle(/.*)?' do + secontext 'httpd_sys_rw_content_t' + end + ``` + + **Adapt a symbolic link**: + + ```ruby + selinux_fcontext '/var/www/symlink_to_webroot' do + secontext 'httpd_sys_rw_content_t' + file_type 'l' + end + ``` + DOC + + property :file_spec, String, + name_property: true, + description: "Path to or regex matching the files or directories to label." + + property :secontext, String, + required: %i{add modify manage}, + description: "SELinux context to assign." + + property :file_type, String, + default: "a", + equal_to: %w{a f d c b s l p}, + description: "The type of the file being labeled." + + action_class do + include Chef::SELinux::CommonHelpers + def current_file_context + file_hash = { + "a" => "all files", + "f" => "regular file", + "d" => "directory", + "c" => "character device", + "b" => "block device", + "s" => "socket", + "l" => "symbolic link", + "p" => "named pipe", + } + + contexts = shell_out!("semanage fcontext -l").stdout.split("\n") + # pull out file label from user:role:type:level context string + contexts.grep(/^#{Regexp.escape(new_resource.file_spec)}\s+#{file_hash[new_resource.file_type]}/) do |c| + c.match(/.+ (?<user>.+):(?<role>.+):(?<type>.+):(?<level>.+)$/)[:type] + # match returns ['foo'] or [], shift converts that to 'foo' or nil + end.shift + end + + # Run restorecon to fix label + # https://github.com/sous-chefs/selinux_policy/pull/72#issuecomment-338718721 + def relabel_files + spec = new_resource.file_spec + escaped = Regexp.escape spec + + # find common path between regex and string + common = if spec == escaped + spec + else + index = spec.size.times { |i| break i if spec[i] != escaped[i] } + ::File.dirname spec[0...index] + end + + # if path is not absolute, ignore it and search everything + common = "/" if common[0] != "/" + + if ::File.exist? common + shell_out!("find #{common.shellescape} -ignore_readdir_race -regextype posix-egrep -regex #{spec.shellescape} -prune -print0 | xargs -0 restorecon -iRv") + end + end + end + + action :manage, description: "Assign the file to the right context regardless of previous state." do + run_action(:add) + run_action(:modify) + end + + action :addormodify, description: "Assign the file context if not set. Update the file context if previously set." do + Chef::Log.warn("The :addormodify action for selinux_fcontext is deprecated and will be removed in a future release. Use the :manage action instead.") + run_action(:manage) + end + + # Create if doesn't exist, do not touch if fcontext is already registered + action :add, description: "Assign the file context if not set." do + if selinux_disabled? + Chef::Log.warn("Unable to add SELinux fcontext #{new_resource.name} as SELinux is disabled") + return + end + + unless current_file_context + converge_by "adding label #{new_resource.secontext} to #{new_resource.file_spec}" do + shell_out!("semanage fcontext -a -f #{new_resource.file_type} -t #{new_resource.secontext} '#{new_resource.file_spec}'") + relabel_files + end + end + end + + # Only modify if fcontext exists & doesn't have the correct label already + action :modify, description: "Update the file context if previously set." do + if selinux_disabled? + Chef::Log.warn("Unable to modify SELinux fcontext #{new_resource.name} as SELinux is disabled") + return + end + + if current_file_context && current_file_context != new_resource.secontext + converge_by "modifying label #{new_resource.secontext} to #{new_resource.file_spec}" do + shell_out!("semanage fcontext -m -f #{new_resource.file_type} -t #{new_resource.secontext} '#{new_resource.file_spec}'") + relabel_files + end + end + end + + # Delete if exists + action :delete, description: "Removes the file context if set. " do + if selinux_disabled? + Chef::Log.warn("Unable to delete SELinux fcontext #{new_resource.name} as SELinux is disabled") + return + end + + if current_file_context + converge_by "deleting label for #{new_resource.file_spec}" do + shell_out!("semanage fcontext -d -f #{new_resource.file_type} '#{new_resource.file_spec}'") + relabel_files + end + end + end + end + end +end
\ No newline at end of file diff --git a/lib/chef/resource/selinux_install.rb b/lib/chef/resource/selinux_install.rb new file mode 100644 index 0000000000..d53a74392a --- /dev/null +++ b/lib/chef/resource/selinux_install.rb @@ -0,0 +1,107 @@ +# +# 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" + +class Chef + class Resource + class SelinuxInstall < Chef::Resource + unified_mode true + + provides :selinux_install + + description "Use **selinux_install** resource to encapsulates the set of selinux packages to install in order to manage selinux. It also ensures the directory `/etc/selinux` is created." + introduced "18.0" + examples <<~DOC + **Default installation**: + + ```ruby + selinux_install 'example' + ``` + + **Install with custom packages**: + + ```ruby + selinux_install 'example' do + packages %w(policycoreutils selinux-policy selinux-policy-targeted) + end + ``` + + **Uninstall** + ```ruby + selinux_install 'example' do + action :remove + end + ``` + DOC + + property :packages, [String, Array], + default: lazy { default_install_packages }, + description: "SELinux packages for system." + + action_class do + def do_package_action(action) + # friendly message for unsupported platforms + raise "The platform #{node["platform"]} is not currently supported by the `selinux_install` resource. Please file an issue at https://github.com/chef/chef/issues with details on the platform this cookbook is running on." if new_resource.packages.nil? + + package "selinux" do + package_name new_resource.packages + action action + end + end + end + + action :install, description: "Install required packages." do + do_package_action(action) + + directory "/etc/selinux" do + owner "root" + group "root" + mode "0755" + action :create + end + end + + action :upgrade, description: "Upgrade required packages." do + do_package_action(a) + end + + action :remove, description: "Remove any SELinux-related packages." do + do_package_action(a) + end + + private + + # + # Get an array of packages to be installed based upon node platform_family + # + # @return [Array] Array of string of package names + def default_install_packages + case node["platform_family"] + when "rhel", "fedora", "amazon" + %w{make policycoreutils selinux-policy selinux-policy-targeted selinux-policy-devel libselinux-utils setools-console} + when "debian" + if node["platform"] == "ubuntu" + if node["platform_version"].to_f == 18.04 + %w{make policycoreutils selinux selinux-basics selinux-policy-default selinux-policy-dev auditd setools} + else + %w{make policycoreutils selinux-basics selinux-policy-default selinux-policy-dev auditd setools} + end + else + %w{make policycoreutils selinux-basics selinux-policy-default selinux-policy-dev auditd setools} + end + end + end + end + end +end diff --git a/lib/chef/resource/selinux_module.rb b/lib/chef/resource/selinux_module.rb new file mode 100644 index 0000000000..49810e4b23 --- /dev/null +++ b/lib/chef/resource/selinux_module.rb @@ -0,0 +1,143 @@ +# +# 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" + +class Chef + class Resource + class SelinuxModule < Chef::Resource + unified_mode true + + provides :selinux_module + + description "Use **selinux_module** module resource to create an SELinux policy module from a cookbook file or content provided as a string." + introduced "18.0" + examples <<~DOC + **Creating SElinux module from .te file located at `files` directory of your cookbook.**: + + ```ruby + selinux_module 'my_policy_module' do + source 'my_policy_module.te' + action :create + end + ``` + DOC + + property :module_name, String, + name_property: true, + description: "Override the module name." + + property :source, String, + description: "Module source file name." + + property :content, String, + description: "Module source as String." + + property :cookbook, String, + description: "Cookbook to source from module source file from(if it is not located in the current cookbook). The default value is the current cookbook.", + desired_state: false + + property :base_dir, String, + default: "/etc/selinux/local", + description: "Directory to create module source file in." + + action_class do + def selinux_module_filepath(type) + path = ::File.join(new_resource.base_dir, "#{new_resource.module_name}") + path.concat(".#{type}") if type + end + + def list_installed_modules + shell_out!("semodule --list-modules").stdout.split("\n").map { |x| x.split(/\s/).first } + end + end + + action :create, description: "Compile a module and install it." do + directory new_resource.base_dir + + if property_is_set?(:content) + file selinux_module_filepath("te") do + content new_resource.content + + mode "0600" + owner "root" + group "root" + + action :create + + notifies :run, "execute[Compiling SELinux modules at '#{new_resource.base_dir}']", :immediately + end + else + cookbook_file selinux_module_filepath("te") do + cookbook new_resource.cookbook + source new_resource.source + + mode "0600" + owner "root" + group "root" + + action :create + + notifies :run, "execute[Compiling SELinux modules at '#{new_resource.base_dir}']", :immediately + end + end + + execute "Compiling SELinux modules at '#{new_resource.base_dir}'" do + cwd new_resource.base_dir + command "make -C #{new_resource.base_dir} -f /usr/share/selinux/devel/Makefile" + timeout 120 + user "root" + + action :nothing + + notifies :run, "execute[Install SELinux module '#{selinux_module_filepath("pp")}']", :immediately + end + + raise "Compilation must have failed, no 'pp' file found at: '#{selinux_module_filepath("pp")}'" unless ::File.exist?(selinux_module_filepath("pp")) + + execute "Install SELinux module '#{selinux_module_filepath("pp")}'" do + command "semodule --install '#{selinux_module_filepath("pp")}'" + action :nothing + end + end + + action :delete, description: "Remove module source files from `/etc/selinux/local`." do + %w{fc if pp te}.each do |type| + next unless ::File.exist?(selinux_module_filepath(type)) + + file selinux_module_filepath(type) do + action :delete + end + end + end + + action :install, description: "Install a compiled module into the system." do + raise "Module must be compiled before it can be installed, no 'pp' file found at: '#{selinux_module_filepath("pp")}'" unless ::File.exist?(selinux_module_filepath("pp")) + + unless list_installed_modules.include? new_resource.module_name + converge_by "Install SELinux module #{selinux_module_filepath("pp")}" do + shell_out!("semodule", "--install", selinux_module_filepath("pp")) + end + end + end + + action :remove, description: "Remove a module from the system." do + if list_installed_modules.include? new_resource.module_name + converge_by "Remove SELinux module #{new_resource.module_name}" do + shell_out!("semodule", "--remove", new_resource.module_name) + end + end + end + end + end +end
\ No newline at end of file diff --git a/lib/chef/resource/selinux_permissive.rb b/lib/chef/resource/selinux_permissive.rb new file mode 100644 index 0000000000..155f4ef390 --- /dev/null +++ b/lib/chef/resource/selinux_permissive.rb @@ -0,0 +1,64 @@ +# +# 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" + +class Chef + class Resource + class SelinuxPermissive < Chef::Resource + unified_mode true + + provides :selinux_permissive + + description "Use **selinux_permissive** resource to allows some types to misbehave without stopping them. Not as good as specific policies, but better than disabling SELinux entirely." + introduced "18.0" + examples <<~DOC + **Disable enforcement on Apache**: + + ```ruby + selinux_permissive 'httpd_t' do + notifies :restart, 'service[httpd]' + end + ``` + DOC + + property :context, String, + name_property: true, + description: "The SELinux context to permit." + + action_class do + def current_permissives + shell_out!("semanage permissive -ln").stdout.split("\n") + end + end + + # Create if doesn't exist, do not touch if permissive is already registered (even under different type) + action :add, description: "Add a permissive, unless already set." do + unless current_permissives.include? new_resource.context + converge_by "adding permissive context #{new_resource.context}" do + shell_out!("semanage permissive -a '#{new_resource.context}'") + end + end + end + + # Delete if exists + action :delete, description: "Remove a permissive, if set." do + if current_permissives.include? new_resource.context + converge_by "deleting permissive context #{new_resource.context}" do + shell_out!("semanage permissive -d '#{new_resource.context}'") + end + end + end + end + end +end
\ No newline at end of file diff --git a/lib/chef/resource/selinux_port.rb b/lib/chef/resource/selinux_port.rb new file mode 100644 index 0000000000..8690adf9b1 --- /dev/null +++ b/lib/chef/resource/selinux_port.rb @@ -0,0 +1,118 @@ +# +# 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 SelinuxPort < Chef::Resource + unified_mode true + + provides :selinux_port + + description "Use **selinux_port** resource to allows assigning a network port to a certain SELinux context, e.g. for running a webserver on a non-standard port." + introduced "18.0" + examples <<~DOC + **Allow nginx/apache to bind to port 5678 by giving it the http_port_t context**: + + ```ruby + selinux_port '5678' do + protocol 'tcp' + secontext 'http_port_t' + end + ``` + DOC + + property :port, [Integer, String], + name_property: true, + regex: /^\d+$/, + description: "Port to modify." + + property :protocol, String, + equal_to: %w{tcp udp}, + required: %i{manage add modify}, + description: "Protocol to modify." + + property :secontext, String, + required: %i{manage add modify}, + description: "SELinux context to assign to the port." + + action_class do + include Chef::SELinux::CommonHelpers + def current_port_context + # use awk to see if the given port is within a reported port range + shell_out!( + <<~CMD + seinfo --portcon=#{new_resource.port} | grep 'portcon #{new_resource.protocol}' | \ + awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' + CMD + ).stdout.split + end + end + + action :manage, description: "Assign the port to the right context regardless of previous state." do + run_action(:add) + run_action(:modify) + end + + action :addormodify, description: "Assigns the port context if not set. Updates the port context if previously set." do + Chef::Log.warn("The :addormodify action for selinux_port is deprecated and will be removed in a future release. Use the :manage action instead.") + run_action(:manage) + end + + # Create if doesn't exist, do not touch if port is already registered (even under different type) + action :add, description: "Assign the port context if not set." do + if selinux_disabled? + Chef::Log.warn("Unable to add SELinux port #{new_resource.name} as SELinux is disabled") + return + end + + if current_port_context.empty? + converge_by "Adding context #{new_resource.secontext} to port #{new_resource.port}/#{new_resource.protocol}" do + shell_out!("semanage port -a -t '#{new_resource.secontext}' -p #{new_resource.protocol} #{new_resource.port}") + end + end + end + + # Only modify port if it exists & doesn't have the correct context already + action :modify, description: "Update the port context if previously set." do + if selinux_disabled? + Chef::Log.warn("Unable to modify SELinux port #{new_resource.name} as SELinux is disabled") + return + end + + if !current_port_context.empty? && !current_port_context.include?(new_resource.secontext) + converge_by "Modifying context #{new_resource.secontext} to port #{new_resource.port}/#{new_resource.protocol}" do + shell_out!("semanage port -m -t '#{new_resource.secontext}' -p #{new_resource.protocol} #{new_resource.port}") + end + end + end + + # Delete if exists + action :delete, description: "Removes the port context if set." do + if selinux_disabled? + Chef::Log.warn("Unable to delete SELinux port #{new_resource.name} as SELinux is disabled") + return + end + + unless current_port_context.empty? + converge_by "Deleting context from port #{new_resource.port}/#{new_resource.protocol}" do + shell_out!("semanage port -d -p #{new_resource.protocol} #{new_resource.port}") + end + end + end + + end + end +end
\ No newline at end of file diff --git a/lib/chef/resource/selinux_state.rb b/lib/chef/resource/selinux_state.rb new file mode 100644 index 0000000000..095a272ef7 --- /dev/null +++ b/lib/chef/resource/selinux_state.rb @@ -0,0 +1,166 @@ +# +# 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 SelinuxState < Chef::Resource + unified_mode true + + provides :selinux_state + + description "Use **selinux_state** resource to manages the SELinux state on the system. It does this by using the `setenforce` command and rendering the `/etc/selinux/config` file from a template." + introduced "18.0" + examples <<~DOC + **Set SELinux state to permissive**: + + ```ruby + selinux_state 'permissive' do + action :permissive + end + ``` + + **Set SELinux state to enforcing**: + + ```ruby + selinux_state 'enforcing' do + action :enforcing + end + ``` + + **Set SELinux state to disabled**: + ```ruby + selinux_state 'disabled' do + action :disabled + end + ``` + DOC + + default_action :nothing + + property :config_file, String, + default: "/etc/selinux/config", + description: "Path to SELinux config file on disk." + + property :persistent, [true, false], + default: true, + description: "Persist status update to the selinux configuration file." + + property :policy, String, + default: lazy { default_policy_platform }, + equal_to: %w{default minimum mls src strict targeted}, + description: "SELinux policy type." + + property :automatic_reboot, [true, false, Symbol], + default: false, + description: "Perform an automatic node reboot if required for state change." + + deprecated_property_alias "temporary", "persistent", "The temporary property was renamed persistent in the 4.0 release of this cookbook. Please update your cookbooks to use the new property name." + + action_class do + include Chef::SELinux::CommonHelpers + def render_selinux_template(action) + Chef::Log.warn("It is advised to set the configuration first to permissive to relabel the filesystem prior to enforcing.") if selinux_disabled? && action == :enforcing + + unless new_resource.automatic_reboot + Chef::Log.warn("Changes from disabled require a reboot.") if selinux_disabled? && %i{enforcing permissive}.include?(action) + Chef::Log.warn("Disabling selinux requires a reboot.") if (selinux_enforcing? || selinux_permissive?) && action == :disabled + end + + template "#{action} selinux config" do + path new_resource.config_file + source debian? ? ::File.expand_path("selinux/selinux_debian.erb", __dir__) : ::File.expand_path("selinux/selinux_default.erb", __dir__) + local true + variables( + selinux: action.to_s, + selinuxtype: new_resource.policy + ) + end + end + + def node_selinux_restart + unless new_resource.automatic_reboot + Chef::Log.warn("SELinux state change to #{action} requires a manual reboot as SELinux is currently #{selinux_state} and automatic reboots are disabled.") + return + end + + outer_action = action + reboot "selinux_state_change" do + delay_mins 1 + reason "SELinux state change to #{outer_action} from #{selinux_state}" + + action new_resource.automatic_reboot.is_a?(Symbol) ? new_resource.automatic_reboot : :reboot_now + end + end + end + + action :enforcing, description: "Set the SELinux state to enforcing." do + unless selinux_disabled? || selinux_enforcing? + execute "selinux-setenforce-enforcing" do + command "/usr/sbin/setenforce 1" + end + end + + if selinux_activate_required? + execute "debian-selinux-activate" do + command "/usr/sbin/selinux-activate" + end + end + + render_selinux_template(action) if new_resource.persistent + node_selinux_restart if state_change_reboot_required? + end + + action :permissive, description: "Set the SELinux state to permissive." do + unless selinux_disabled? || selinux_permissive? + execute "selinux-setenforce-permissive" do + command "/usr/sbin/setenforce 0" + end + end + + if selinux_activate_required? + execute "debian-selinux-activate" do + command "/usr/sbin/selinux-activate" + end + end + + render_selinux_template(action) if new_resource.persistent + node_selinux_restart if state_change_reboot_required? + end + + action :disabled, description: "Set the SELinux state to disabled. **NOTE**: Switching to or from disabled requires a reboot!" do + raise "A non-persistent change to the disabled SELinux status is not possible." unless new_resource.persistent + + render_selinux_template(action) + node_selinux_restart if state_change_reboot_required? + end + + private + + # + # Decide default policy platform based upon platform_family + # + # @return [String] Policy platform name + def default_policy_platform + case node["platform_family"] + when "rhel", "fedora", "amazon" + "targeted" + when "debian" + "default" + end + end + end + end +end
\ No newline at end of file diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index ac5ec5d8e0..0d310f8bea 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -124,6 +124,13 @@ require_relative "resource/route" require_relative "resource/ruby" require_relative "resource/ruby_block" require_relative "resource/script" +require_relative "resource/selinux_boolean" +require_relative "resource/selinux_fcontext" +require_relative "resource/selinux_install" +require_relative "resource/selinux_module" +require_relative "resource/selinux_permissive" +require_relative "resource/selinux_port" +require_relative "resource/selinux_state" require_relative "resource/service" require_relative "resource/sudo" require_relative "resource/sysctl" |