summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorneha-p6 <neha.pansare@progress.com>2022-04-11 11:00:57 +0530
committerGitHub <noreply@github.com>2022-04-11 11:00:57 +0530
commitd3dda786b2a6d61025288c002ae7b5b60c499f92 (patch)
tree2e48a0e853c05ca404be357ba2c928d491ec7b01 /lib
parentd3e922a67abec71825d6f6c6ac8fd4ef9fc53019 (diff)
downloadchef-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.rb47
-rw-r--r--lib/chef/resource/selinux/selinux_debian.erb18
-rw-r--r--lib/chef/resource/selinux/selinux_default.erb15
-rw-r--r--lib/chef/resource/selinux_boolean.rb101
-rw-r--r--lib/chef/resource/selinux_fcontext.rb160
-rw-r--r--lib/chef/resource/selinux_install.rb107
-rw-r--r--lib/chef/resource/selinux_module.rb143
-rw-r--r--lib/chef/resource/selinux_permissive.rb64
-rw-r--r--lib/chef/resource/selinux_port.rb118
-rw-r--r--lib/chef/resource/selinux_state.rb166
-rw-r--r--lib/chef/resources.rb7
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"