From b35112c2bb5514d656331e0dc072eb4f5141a15a Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Thu, 2 Sep 2021 11:59:27 -0700 Subject: [PATCH] Import resources from selinux_policy (#79) * add resources + integration tests from selinux_policy * Use inspec profiles instead of raw tests * use native shell_out instead of execute resources in port resource * use native shell_out instead of execute resources in fcontext resource * Use shell_out! helper in permissive resource * update documentation for new resources * add spec tests for all resources * appease yamllint * fix module list on c7 * Standardize documentation format * Review fixes * fix tests * appease mdl * Fix test idempotency * add missing header to resource * add missing documentation links to README Signed-off-by: Robert Detjens --- CHANGELOG.md | 8 + README.md | 3 + documentation/selinux_boolean.md | 14 +- documentation/selinux_fcontext.md | 48 ++++++ documentation/selinux_install.md | 27 ++-- documentation/selinux_module.md | 27 ++-- documentation/selinux_permissive.md | 29 ++++ documentation/selinux_port.md | 32 ++++ documentation/selinux_state.md | 21 ++- kitchen.yml | 35 ++-- libraries/install.rb | 8 +- libraries/module.rb | 21 --- resources/fcontext.rb | 132 +++++++++++++++ resources/install.rb | 6 +- resources/module.rb | 34 ++-- resources/permissive.rb | 46 ++++++ resources/port.rb | 98 +++++++++++ spec/unit/resources/boolean_spec.rb | 52 ++++++ spec/unit/resources/fcontext_spec.rb | 81 ++++++++++ spec/unit/resources/install_spec.rb | 60 +++++++ spec/unit/resources/module_spec.rb | 152 ++++++++++++++++++ spec/unit/resources/permissive_spec.rb | 52 ++++++ spec/unit/resources/port_spec.rb | 85 ++++++++++ spec/{ => unit}/resources/state_spec.rb | 6 +- .../files/default/dirtest_module/dirtest.fc | 1 + .../files/default/dirtest_module/dirtest.if | 16 ++ .../files/default/dirtest_module/dirtest.te | 19 +++ test/cookbooks/selinux_test/metadata.rb | 1 + .../selinux_test/recipes/fcontext.rb | 23 +++ .../recipes/permissive_resource.rb | 3 + test/cookbooks/selinux_test/recipes/port.rb | 11 ++ .../common/controls/install_control.rb | 17 ++ test/integration/common/inspec.yml | 3 + test/integration/common/install_spec.rb | 13 -- .../disabled/controls/disabled_control.rb | 21 +++ test/integration/disabled/inspec.yml | 7 + test/integration/disabled/state_spec.rb | 15 -- test/integration/enforcing/boolean_spec.rb | 19 --- .../enforcing/controls/boolean_control.rb | 23 +++ .../enforcing/controls/enforcing_control.rb | 26 +++ .../enforcing/controls/module_control.rb | 23 +++ test/integration/enforcing/inspec.yml | 7 + test/integration/enforcing/module_spec.rb | 19 --- test/integration/enforcing/state_spec.rb | 20 --- .../fcontext/controls/fcontext_control.rb | 21 +++ test/integration/fcontext/inspec.yml | 7 + test/integration/permissive/boolean_spec.rb | 19 --- .../permissive/controls/boolean_control.rb | 23 +++ .../permissive/controls/module_control.rb | 9 ++ .../permissive/controls/permissive_control.rb | 22 +++ test/integration/permissive/inspec.yml | 7 + test/integration/permissive/module_spec.rb | 5 - test/integration/permissive/state_spec.rb | 16 -- .../controls/permissive_control.rb | 10 ++ .../permissive_resource/inspec.yml | 7 + .../integration/port/controls/port_control.rb | 14 ++ test/integration/port/inspec.yml | 7 + 57 files changed, 1307 insertions(+), 224 deletions(-) create mode 100644 documentation/selinux_fcontext.md create mode 100644 documentation/selinux_permissive.md create mode 100644 documentation/selinux_port.md delete mode 100644 libraries/module.rb create mode 100644 resources/fcontext.rb create mode 100644 resources/permissive.rb create mode 100644 resources/port.rb create mode 100644 spec/unit/resources/boolean_spec.rb create mode 100644 spec/unit/resources/fcontext_spec.rb create mode 100644 spec/unit/resources/install_spec.rb create mode 100644 spec/unit/resources/module_spec.rb create mode 100644 spec/unit/resources/permissive_spec.rb create mode 100644 spec/unit/resources/port_spec.rb rename spec/{ => unit}/resources/state_spec.rb (78%) create mode 100644 test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.fc create mode 100644 test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.if create mode 100644 test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.te create mode 100644 test/cookbooks/selinux_test/recipes/fcontext.rb create mode 100644 test/cookbooks/selinux_test/recipes/permissive_resource.rb create mode 100644 test/cookbooks/selinux_test/recipes/port.rb create mode 100644 test/integration/common/controls/install_control.rb create mode 100644 test/integration/common/inspec.yml delete mode 100644 test/integration/common/install_spec.rb create mode 100644 test/integration/disabled/controls/disabled_control.rb create mode 100644 test/integration/disabled/inspec.yml delete mode 100644 test/integration/disabled/state_spec.rb delete mode 100644 test/integration/enforcing/boolean_spec.rb create mode 100644 test/integration/enforcing/controls/boolean_control.rb create mode 100644 test/integration/enforcing/controls/enforcing_control.rb create mode 100644 test/integration/enforcing/controls/module_control.rb create mode 100644 test/integration/enforcing/inspec.yml delete mode 100644 test/integration/enforcing/module_spec.rb delete mode 100644 test/integration/enforcing/state_spec.rb create mode 100644 test/integration/fcontext/controls/fcontext_control.rb create mode 100644 test/integration/fcontext/inspec.yml delete mode 100644 test/integration/permissive/boolean_spec.rb create mode 100644 test/integration/permissive/controls/boolean_control.rb create mode 100644 test/integration/permissive/controls/module_control.rb create mode 100644 test/integration/permissive/controls/permissive_control.rb create mode 100644 test/integration/permissive/inspec.yml delete mode 100644 test/integration/permissive/module_spec.rb delete mode 100644 test/integration/permissive/state_spec.rb create mode 100644 test/integration/permissive_resource/controls/permissive_control.rb create mode 100644 test/integration/permissive_resource/inspec.yml create mode 100644 test/integration/port/controls/port_control.rb create mode 100644 test/integration/port/inspec.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 22349eb..2b51fc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ This file is used to list changes made in each version of the selinux cookbook. ## Unreleased +- Import `selinux_policy` resources into this cookbook (`_fcontext`, `_permissive`, and `_port`) + - `selinux_policy_module` not imported since it is a duplicate of `selinux_module` + +### Deprecations + +- `selinux_fcontext` action `addormodify` renamed to `manage` +- `selinux_port` action `addormodify` renamed to `manage` + ## 5.1.1 - *2021-08-30* - Standardise files with files in sous-chefs/repo-management diff --git a/README.md b/README.md index 610d256..9a452bf 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,11 @@ Disable SELinux only if you plan to not use it. Use `Permissive` mode if you jus The following resources are provided: - [selinux_boolean](documentation/selinux_boolean.md) +- [selinux_fcontext](documentation/selinux_fcontext.md) - [selinux_install](documentation/selinux_install.md) - [selinux_module](documentation/selinux_module.md) +- [selinux_permissive](documentation/selinux_permissive.md) +- [selinux_port](documentation/selinux_port.md) - [selinux_state](documentation/selinux_state.md) ## Maintainers diff --git a/documentation/selinux_boolean.md b/documentation/selinux_boolean.md index 6a31aa9..24b4796 100644 --- a/documentation/selinux_boolean.md +++ b/documentation/selinux_boolean.md @@ -8,15 +8,17 @@ Introduced: v4.0.0 ## Actions -- `:set` +| Action | Description | +| ------ | ---------------------------- | +| `:set` | Set the state of the boolean | ## Properties -| Name | Type | Default | Description | Allowed Values | -| ------------ | ---------------------------- | ------- | ----------------------------------------------- | -------------- | -| `boolean` | String | | SELinux boolean to set | | -| `value` | Integer, String, true, false | | SELinux boolean value | `on`, `off` | -| `persistent` | true, false | true | Set to true for value setting to survive reboot | | +| Name | Type | Default | Description | +| ------------ | -------------------------------- | ------------- | ----------------------------------------------- | +| `boolean` | String | Resource name | SELinux boolean to set | +| `value` | `true`, `false`, `'on'`, `'off'` | | SELinux boolean value | +| `persistent` | `true`, `false` | `true` | Set to true for value setting to survive reboot | ## Examples diff --git a/documentation/selinux_fcontext.md b/documentation/selinux_fcontext.md new file mode 100644 index 0000000..959f883 --- /dev/null +++ b/documentation/selinux_fcontext.md @@ -0,0 +1,48 @@ +[Back to resource list](../README.md#resources) + +# selinux_fcontext + +Set the SELinux context of files with `semanage fcontext`. + +## Actions + +| Action | Description | +| --------- | ------------------------------------------------------------------------------- | +| `:manage` | *(Default)* Assigns the file to the right context regardless of previous state. | +| `:add` | Assigns the file context if not set.(`-a`) | +| `:modify` | Updates the file context if previously set.(`-m`) | +| `:delete` | Removes the file context if set. (`-d`) | + +## Properties + +| Name | Type | Default | Description | +| ----------- | ------ | --------------- | ---------------------------------------------------------------------------- | +| `file_spec` | String | Resource name | Path or regular expression to files to modify. | +| `secontext` | String | | The SELinux context to assign the file to. | +| `file_type` | String | `a` (all files) | Restrict the resource to only modifying specific file types. See list below. | + +Supported file types: + +- **`a`** - All files +- **`f`** - Regular files +- **`d`** - Directory +- **`c`** - Character device +- **`b`** - Block device +- **`s`** - Socket +- **`l`** - Symbolic link +- **`p`** - Named pipe + +## Examples + +```ruby +# Allow http servers (e.g. nginx/apache) to modify moodle files +selinux_policy_fcontext '/var/www/moodle(/.*)?' do + secontext 'httpd_sys_rw_content_t' +end + +# Adapt a symbolic link +selinux_policy_fcontext '/var/www/symlink_to_webroot' do + secontext 'httpd_sys_rw_content_t' + file_type 'l' +end +``` diff --git a/documentation/selinux_install.md b/documentation/selinux_install.md index 26d3b9d..98633af 100644 --- a/documentation/selinux_install.md +++ b/documentation/selinux_install.md @@ -8,39 +8,38 @@ Introduced: v4.0.0 ## Actions -- `:install` -- `:upgrade` -- `:remove` +| Action | Description | +| ---------- | ------------------------------------- | +| `:install` | *(Default)* Install required packages | +| `:upgrade` | Upgrade required packages | +| `:remove` | Remove any SELinux-related packages | ## Properties -| Name | Type | Default | Description | -| ---------- | ------------- | -------------------------- | --------------------------- | -| `packages` | String, Array | `default_install_packages` | SELinux packages for system | +| Name | Type | Default | Description | +| ---------- | ------------- | --------------------------------------------------------- | --------------------------- | +| `packages` | String, Array | see [`default_install_packages`](../libraries/install.rb) | SELinux packages for system | ## Examples ### Default installation ```ruby -selinux_install '' do - action :install -end +selinux_install 'example' ``` -### Install with excluded packages +### Install with custom packages ```ruby -selinux_install '' do - packages_exclude %w(policycoreutils selinux-policy selinux-policy-targeted ) - action :install +selinux_install 'example' do + packages %w(policycoreutils selinux-policy selinux-policy-targeted) end ``` ### Uninstall ```ruby -selinux_install '' do +selinux_install 'example' do action :remove end ``` diff --git a/documentation/selinux_module.md b/documentation/selinux_module.md index b551246..38a4e47 100644 --- a/documentation/selinux_module.md +++ b/documentation/selinux_module.md @@ -8,20 +8,22 @@ Introduced: v4.0.0 ## Actions -- `:create` -- `:delete` -- `:install` -- `:remove` +| Action | Description | +| ---------- | ---------------------------------------------------- | +| `:create` | *(Default)* Compile a module and install it | +| `:delete` | Remove module source files from `/etc/selinux/local` | +| `:install` | Install a compiled module into the system | +| `:remove` | Remove a module from the system | ## Properties | Name | Type | Default | Description | | ------------- | ------ | -------------------- | ----------------------------------------------- | -| `cookbook` | String | | Cookbook to source from module source file from | +| `module_name` | String | Resource name | Override the module name | +| `content` | String | | Module source as text | | `source` | String | | Module source file name | -| `content` | String | `nil` | Module source as String | | `base_dir` | String | `/etc/selinux/local` | Directory to create module source file in | -| `module_name` | String | `name` | Override the module name | +| `cookbook` | String | | Cookbook to source from module source file from | ## Examples @@ -30,8 +32,7 @@ selinux_module 'test_create' do cookbook 'selinux_test' source 'test.te' module_name 'test' - - action :create + action :install end ``` @@ -49,15 +50,15 @@ Consider the following steps to obtain a `.te` file, the rule description format 1. Add `selinux` to your `metadata.rb`, as for instance: `depends 'selinux', '>= 0.10.0'`; 2. Run your SELinux workflow, and add `.te` files on your cookbook files, preferably under `files/default/selinux` directory; -3. Write recipes using `selinux_module` provider; +3. Write recipes using `selinux_module` resource; ### SELinux `audit2allow` Workflow -This provider was written with the intention of matching the workflow of `audit2allow` (provided by package `policycoreutils`), which basically will be: +This resource was written with the intention of matching the workflow of `audit2allow` (provided by package `policycoreutils`), which basically will be: -1. Test application and inspect `/var/log/audit/audit.log` log-file with a command like this basic example: `grep AVC /var/log/audit/audit.log |audit2allow -M my_application`; +1. Test application and inspect `/var/log/audit/audit.log` log-file with a command like this basic example: `grep AVC /var/log/audit/audit.log | audit2allow -M my_application`; 2. Save `my_application.te` SELinux module source, copy into your cookbook under `files/default/selinux/my_application.te`; -3. Make use of `selinux` provider on a recipe, after adding it as a dependency; +3. Make use of `selinux` resource on a recipe, after adding it as a dependency; For example, add the following on the recipe level: diff --git a/documentation/selinux_permissive.md b/documentation/selinux_permissive.md new file mode 100644 index 0000000..5c5ee9f --- /dev/null +++ b/documentation/selinux_permissive.md @@ -0,0 +1,29 @@ +[Back to resource list](../README.md#resources) + +# selinux_permissive + +Allows some types to misbehave without stopping them. Not as good as specific policies, but better than disabling SELinux entirely. + +> This does not set the SELinux state to permissive! Use [`selinux_state`](selinux_state.md) for that. + +## Actions + +| Action | Description | +| --------- | -------------------------------------------------- | +| `:add` | *(Default)* Adds a permissive, unless already set. | +| `:delete` | Removes a permissive, if set. | + +## Properties + +| Name | Type | Default | Description | +| --------- | ------ | ------------- | ------------------------------------------- | +| `context` | String | Resource name | Name of the context to disable SELinux for. | + +## Examples + +```ruby +# Disable enforcement on Apache +selinux_permissive 'httpd_t' do + notifies :restart, 'service[httpd]' +end +``` diff --git a/documentation/selinux_port.md b/documentation/selinux_port.md new file mode 100644 index 0000000..0621ddb --- /dev/null +++ b/documentation/selinux_port.md @@ -0,0 +1,32 @@ +[Back to resource list](../README.md#resources) + +# selinux_policy_port + +Allows assigning a network port to a certain SELinux context, e.g. for running a webserver on a non-standard port. + +## Actions + +| Action | Description | +| --------- | ------------------------------------------------------------------------------- | +| `:manage` | *(Default)* Assigns the port to the right context regardless of previous state. | +| `:add` | Assigns the port context if not set.(`-a`) | +| `:modify` | Updates the port context if previously set.(`-m`) | +| `:delete` | Removes the port context if set. (`-d`) | + +## Properties + +| Name | Type | Default | Description | +| ----------- | ------ | ------------- | ------------------------------------------ | +| `port` | String | Resource name | The port in question. | +| `protocol` | String | | Either `tcp` or `udp`. | +| `secontext` | String | | The SELinux context to assign the port to. | + +## Examples + +```ruby +# Allow nginx/apache to bind to port 5678 by giving it the http_port_t context +selinux_policy_port '5678' do + protocol 'tcp' + secontext 'http_port_t' +end +``` diff --git a/documentation/selinux_state.md b/documentation/selinux_state.md index c4c888d..3271040 100644 --- a/documentation/selinux_state.md +++ b/documentation/selinux_state.md @@ -8,17 +8,22 @@ Introduced: v4.0.0 ## Actions -- `:disabled` -- `:enforcing` -- `:permissive` +| Action | Description | +| ------------- | ---------------------------------------------- | +| `:enforcing` | *(Default)* Set the SELinux state to enforcing | +| `:permissive` | Set the state to permissive | +| `:disabled` | Set the state to disabled | +` +> ⚠ Switching to or from `disabled` requires a reboot! ## Properties -| Name | Type | Default | Description | -| ------------- | ----------- | --------------------- | ------------------------------------------------------- | -| `config_file` | String | `/etc/selinux/config` | Path to SELinux config file on disk | -| `persistent` | true, false | `true` | Persist status update to the selinux configuration file | -| `policy` | String | `targeted` | SELinux policy type | +| Name | Type | Default | Description | +| ------------------ | ------------------- | --------------------- | ------------------------------------------------------------------ | +| `config_file` | String | `/etc/selinux/config` | Path to SELinux config file on disk | +| `persistent` | true, false | `true` | Persist status update to the selinux configuration file | +| `policy` | String | `targeted` | SELinux policy type | +| `automatic_reboot` | true, false, Symbol | `false` | Whether to automatically reboot the node if needed to change state | ## Examples diff --git a/kitchen.yml b/kitchen.yml index 41826c3..9b064f3 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -6,6 +6,8 @@ provisioner: name: chef_zero product_name: chef chef_license: accept-no-persist + multiple_converge: 2 + enforce_idempotency: true deprecations_as_errors: true product_version: <%= ENV['CHEF_VERSION'] || 'latest' %> log_level: <%= ENV['CHEF_LOG_LEVEL'] || 'auto' %> @@ -35,27 +37,34 @@ suites: - recipe[selinux_test::enforcing] - recipe[selinux_test::debian_enforcing_prepare] - recipe[selinux_test::module_create] + - recipe[selinux_test::module_remove] - recipe[selinux_test::boolean] - verifier: - inspec_tests: - - test/integration/common - - test/integration/enforcing + provisioner: + # not idempotent on debian/ubuntu due to debian_enforcing_prepare & module_create/remove + multiple_converge: 1 + enforce_idempotency: false - name: permissive run_list: - recipe[selinux_test::install] - recipe[selinux::permissive] - recipe[selinux_test::module_create] - - recipe[selinux_test::module_remove] - recipe[selinux_test::boolean] - verifier: - inspec_tests: - - test/integration/common - - test/integration/permissive - name: disabled run_list: - recipe[selinux_test::install] - recipe[selinux::disabled] - verifier: - inspec_tests: - - test/integration/common - - test/integration/disabled + - name: port + run_list: + - recipe[selinux_test::install] + - recipe[selinux::permissive] + - recipe[selinux_test::port] + - name: fcontext + run_list: + - recipe[selinux_test::install] + - recipe[selinux::permissive] + - recipe[selinux_test::fcontext] + - name: permissive_resource + run_list: + - recipe[selinux_test::install] + - recipe[selinux::permissive] + - recipe[selinux_test::permissive_resource] diff --git a/libraries/install.rb b/libraries/install.rb index 587dc44..b99c64f 100644 --- a/libraries/install.rb +++ b/libraries/install.rb @@ -4,16 +4,16 @@ module InstallHelpers 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) + %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) + %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) + %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) + %w(make policycoreutils selinux-basics selinux-policy-default selinux-policy-dev auditd setools) end end end diff --git a/libraries/module.rb b/libraries/module.rb deleted file mode 100644 index f332e96..0000000 --- a/libraries/module.rb +++ /dev/null @@ -1,21 +0,0 @@ -module SELinux - module Cookbook - module ModuleHelpers - include Chef::Mixin::ShellOut - - extend self - - def installed?(module_name) - return true if list_installed_modules.include?(module_name) - - false - end - - private - - def list_installed_modules - shell_out!('/usr/sbin/semodule --list-modules', returns: [0]).stdout.split("\n").map { |m| m.split("\t").first } - end - end - end -end diff --git a/resources/fcontext.rb b/resources/fcontext.rb new file mode 100644 index 0000000..ac2b4d6 --- /dev/null +++ b/resources/fcontext.rb @@ -0,0 +1,132 @@ +# +# Cookbook:: selinux +# Resource:: fcontext +# +# 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. +# + +unified_mode true + +property :file_spec, String, + name_property: true, + description: 'Path to or regex matching the files or directoriesto 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 SELinux::Cookbook::StateHelpers + + 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(/.+ (?.+):(?.+):(?.+):(?.+)$/)[: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 do + run_action(:add) + run_action(:modify) +end + +action :addormodify 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 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 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 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 diff --git a/resources/install.rb b/resources/install.rb index 192ea36..4571b54 100644 --- a/resources/install.rb +++ b/resources/install.rb @@ -47,4 +47,8 @@ def do_package_action(action) end end -%i(upgrade remove).each { |action_type| send(:action, action_type) { do_package_action(action) } } +%i(upgrade remove).each do |a| + action a do + do_package_action(a) + end +end diff --git a/resources/module.rb b/resources/module.rb index 06966a7..9526bd3 100644 --- a/resources/module.rb +++ b/resources/module.rb @@ -18,9 +18,9 @@ unified_mode true -property :cookbook, String, - default: lazy { cookbook_name }, - description: 'Cookbook to source from module source file from' +property :module_name, String, + name_property: true, + description: 'Override the module name' property :source, String, description: 'Module source file name' @@ -28,20 +28,22 @@ property :content, String, description: 'Module source as String' +property :cookbook, String, + default: lazy { cookbook_name }, + description: 'Cookbook to source from module source file from' + property :base_dir, String, default: '/etc/selinux/local', description: 'Directory to create module source file in' -property :module_name, String, - default: lazy { name }, - description: 'Override the module name' - 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 - path + def list_installed_modules + shell_out!('semodule --list-modules').stdout.split("\n").map { |x| x.split(/\s/).first } end end @@ -107,15 +109,17 @@ def selinux_module_filepath(type) action :install 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')) - execute "Install SELinux module '#{selinux_module_filepath('pp')}'" do - command "semodule --install '#{selinux_module_filepath('pp')}'" - action :nothing + 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 do - execute "Remove SELinux module: '#{new_resource.module_name}'" do - command "semodule --remove='#{new_resource.module_name}'" - action :run - end if SELinux::Cookbook::ModuleHelpers.installed?(new_resource.module_name) + 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 diff --git a/resources/permissive.rb b/resources/permissive.rb new file mode 100644 index 0000000..161b02c --- /dev/null +++ b/resources/permissive.rb @@ -0,0 +1,46 @@ +# +# Cookbook:: selinux +# Resource:: permissive +# +# 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. +# + +unified_mode true + +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 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 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 diff --git a/resources/port.rb b/resources/port.rb new file mode 100644 index 0000000..83cbd5e --- /dev/null +++ b/resources/port.rb @@ -0,0 +1,98 @@ +# +# Cookbook:: selinux +# Resource:: port +# +# 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. +# + +unified_mode true + +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 SELinux::Cookbook::StateHelpers + + 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.strip + end +end + +action :manage do + run_action(:add) + run_action(:modify) +end + +action :addormodify 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 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 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 != 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 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 diff --git a/spec/unit/resources/boolean_spec.rb b/spec/unit/resources/boolean_spec.rb new file mode 100644 index 0000000..f193140 --- /dev/null +++ b/spec/unit/resources/boolean_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe 'selinux_boolean' do + step_into :selinux_boolean + platform 'centos' + + stubs_for_provider('selinux_boolean[test]') do |provider| + allow(provider).to receive_shell_out('getenforce', stdout: 'Permissive') + end + + context 'when boolean is set to incorrect value' do + recipe do + selinux_boolean 'test' do + value 'on' + end + end + + stubs_for_resource('selinux_boolean[test]') do |resource| + allow(resource).to receive_shell_out('getsebool test', stdout: 'off') + end + + # this is what actually checks that the bool was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_boolean[test]') do |provider| + allow(provider).to receive_shell_out('setsebool -P test on') + end + + # needed to have the test run + it { is_expected.to set_selinux_boolean('test') } + end + + context 'when boolean is set to correct value' do + recipe do + selinux_boolean 'test' do + value 'on' + end + end + + stubs_for_resource('selinux_boolean[test]') do |resource| + allow(resource).to receive_shell_out('getsebool test', stdout: 'on') + end + + # dont stub the set command since the resource should not actually + # shell out for it since it is already set to the correct value + # stubs_for_provider("selinux_boolean[test]") do |provider| + # allow(provider).to receive_shell_out('setsebool -P test on') + # end + + # needed to have the test run + it { is_expected.to set_selinux_boolean('test') } + end +end diff --git a/spec/unit/resources/fcontext_spec.rb b/spec/unit/resources/fcontext_spec.rb new file mode 100644 index 0000000..27949c9 --- /dev/null +++ b/spec/unit/resources/fcontext_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe 'selinux_fcontext' do + step_into :selinux_fcontext + platform 'centos' + + stubs_for_provider('selinux_fcontext[/test]') do |provider| + allow(provider).to receive_shell_out('getenforce', stdout: 'Permissive') + end + + recipe do + selinux_fcontext '/test' do + secontext 'foo' + action %i(manage add modify delete) + end + end + + context 'when not set' do + stubs_for_provider('selinux_fcontext[/test]') do |provider| + allow(provider).to receive_shell_out('semanage fcontext -l', stdout: <<~EOF) + /other/files all files user:role:type:level + EOF + end + + # this is what actually checks that the fcontext was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_fcontext[/test]') do |provider| + # when not set, only add calls (-a) should happen + allow(provider).to receive_shell_out("semanage fcontext -a -f a -t foo '/test'") + end + + # needed to have the test run + it { is_expected.to manage_selinux_fcontext('/test') } + it { is_expected.to add_selinux_fcontext('/test') } + it { is_expected.to modify_selinux_fcontext('/test') } + it { is_expected.to delete_selinux_fcontext('/test') } + end + + context 'when set to incorrect value' do + stubs_for_provider('selinux_fcontext[/test]') do |provider| + allow(provider).to receive_shell_out('semanage fcontext -l', stdout: <<~EOF) + /test all files user:role:type:level + EOF + end + + # this is what actually checks that the fcontext was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_fcontext[/test]') do |provider| + # when set but incorrect, only modify calls (-m) and delete calls (-d) should happen + allow(provider).to receive_shell_out("semanage fcontext -m -f a -t foo '/test'") + allow(provider).to receive_shell_out("semanage fcontext -d -f a '/test'") + end + + # needed to have the test run + it { is_expected.to manage_selinux_fcontext('/test') } + it { is_expected.to add_selinux_fcontext('/test') } + it { is_expected.to modify_selinux_fcontext('/test') } + it { is_expected.to delete_selinux_fcontext('/test') } + end + + context 'when set to correct value' do + stubs_for_provider('selinux_fcontext[/test]') do |provider| + allow(provider).to receive_shell_out('semanage fcontext -l', stdout: <<~EOF) + /test all files user:role:foo:level + EOF + end + + # this is what actually checks that the fcontext was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_fcontext[/test]') do |provider| + # when set correctly, only delete calls (-d) should happen + allow(provider).to receive_shell_out("semanage fcontext -d -f a '/test'") + end + + # needed to have the test run + it { is_expected.to manage_selinux_fcontext('/test') } + it { is_expected.to add_selinux_fcontext('/test') } + it { is_expected.to modify_selinux_fcontext('/test') } + it { is_expected.to delete_selinux_fcontext('/test') } + end +end diff --git a/spec/unit/resources/install_spec.rb b/spec/unit/resources/install_spec.rb new file mode 100644 index 0000000..c3bb60a --- /dev/null +++ b/spec/unit/resources/install_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe 'selinux_install' do + step_into :selinux_install + platform 'centos' + + recipe do + selinux_install 'test' + end + + context 'on centos' do + platform 'centos' + + it do + is_expected.to install_package('selinux').with( + package_name: %w(make policycoreutils selinux-policy selinux-policy-targeted selinux-policy-devel libselinux-utils setools-console) + ) + end + end + + context 'on fedora' do + platform 'fedora' + + it do + is_expected.to install_package('selinux').with( + package_name: %w(make policycoreutils selinux-policy selinux-policy-targeted selinux-policy-devel libselinux-utils setools-console) + ) + end + end + + context 'on debian' do + platform 'debian' + + it do + is_expected.to install_package('selinux').with( + package_name: %w(make policycoreutils selinux-basics selinux-policy-default selinux-policy-dev auditd setools) + ) + end + end + + context 'on ubuntu 18.04' do + platform 'ubuntu', '18.04' + + it do + is_expected.to install_package('selinux').with( + package_name: %w(make policycoreutils selinux selinux-basics selinux-policy-default selinux-policy-dev auditd setools) + ) + end + end + + context 'on ubuntu 20.04' do + platform 'ubuntu', '20.04' + + it do + is_expected.to install_package('selinux').with( + package_name: %w(make policycoreutils selinux-basics selinux-policy-default selinux-policy-dev auditd setools) + ) + end + end +end diff --git a/spec/unit/resources/module_spec.rb b/spec/unit/resources/module_spec.rb new file mode 100644 index 0000000..dadfd13 --- /dev/null +++ b/spec/unit/resources/module_spec.rb @@ -0,0 +1,152 @@ +require 'spec_helper' + +describe 'selinux_module' do + step_into :selinux_module + platform 'centos' + + before do + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(%r{/etc/selinux/local/.+.pp}).and_return(true) + end + + context 'create from content' do + recipe do + selinux_module 'test_content' do + content 'some content for the module' + end + end + + it do + is_expected.to create_file('/etc/selinux/local/test_content.te').with( + content: 'some content for the module' + ) + end + + it do + expect(chef_run.file('/etc/selinux/local/test_content.te')).to \ + notify("execute[Compiling SELinux modules at '/etc/selinux/local']").to(:run).immediately + end + + it do + is_expected.to nothing_execute("Compiling SELinux modules at '/etc/selinux/local'").with( + cwd: '/etc/selinux/local', + command: 'make -C /etc/selinux/local -f /usr/share/selinux/devel/Makefile' + ) + end + + it do + expect(chef_run.execute("Compiling SELinux modules at '/etc/selinux/local'")).to \ + notify("execute[Install SELinux module '/etc/selinux/local/test_content.pp']").to(:run).immediately + end + + it do + is_expected.to nothing_execute("Install SELinux module '/etc/selinux/local/test_content.pp'").with( + command: "semodule --install '/etc/selinux/local/test_content.pp'" + ) + end + end + + context 'create from file' do + recipe do + selinux_module 'test_file' do + source 'a_test_file.te' + cookbook 'foo' + end + end + + it do + is_expected.to create_cookbook_file('/etc/selinux/local/test_file.te').with( + source: 'a_test_file.te', + cookbook: 'foo' + ) + end + + it do + expect(chef_run.cookbook_file('/etc/selinux/local/test_file.te')).to \ + notify("execute[Compiling SELinux modules at '/etc/selinux/local']").to(:run).immediately + end + + it do + is_expected.to nothing_execute("Compiling SELinux modules at '/etc/selinux/local'").with( + cwd: '/etc/selinux/local', + command: 'make -C /etc/selinux/local -f /usr/share/selinux/devel/Makefile' + ) + end + + it do + expect(chef_run.execute("Compiling SELinux modules at '/etc/selinux/local'")).to \ + notify("execute[Install SELinux module '/etc/selinux/local/test_file.pp']").to(:run).immediately + end + + it do + is_expected.to nothing_execute("Install SELinux module '/etc/selinux/local/test_file.pp'").with( + command: "semodule --install '/etc/selinux/local/test_file.pp'" + ) + end + end + + context 'delete' do + before do + allow(File).to receive(:exist?).with(%r{/etc/selinux/local/test.+}).and_return(true) + end + + recipe do + selinux_module 'test' do + action :delete + end + end + + it { is_expected.to delete_file('/etc/selinux/local/test.fc') } + it { is_expected.to delete_file('/etc/selinux/local/test.if') } + it { is_expected.to delete_file('/etc/selinux/local/test.pp') } + it { is_expected.to delete_file('/etc/selinux/local/test.te') } + end + + context 'install' do + recipe do + selinux_module 'installed' do + action :install + end + + selinux_module 'gone' do + action :install + end + end + + stubs_for_provider('selinux_module[installed]') do |provider| + allow(provider).to receive_shell_out('semodule --list-modules', stdout: 'installed') + end + + stubs_for_provider('selinux_module[gone]') do |provider| + allow(provider).to receive_shell_out('semodule --list-modules', stdout: 'other') + allow(provider).to receive_shell_out('semodule --install \'/etc/selinux/local/gone.pp\'') + end + + it { is_expected.to install_selinux_module('installed') } + it { is_expected.to install_selinux_module('gone') } + end + + context 'remove' do + recipe do + selinux_module 'installed' do + action :remove + end + + selinux_module 'gone' do + action :remove + end + end + + stubs_for_provider('selinux_module[installed]') do |provider| + allow(provider).to receive_shell_out('semodule --list-modules', stdout: 'installed') + allow(provider).to receive_shell_out('semodule --remove \'installed\'') + end + + stubs_for_provider('selinux_module[gone]') do |provider| + allow(provider).to receive_shell_out('semodule --list-modules', stdout: 'other') + end + + it { is_expected.to remove_selinux_module('installed') } + it { is_expected.to remove_selinux_module('gone') } + end +end diff --git a/spec/unit/resources/permissive_spec.rb b/spec/unit/resources/permissive_spec.rb new file mode 100644 index 0000000..adebc39 --- /dev/null +++ b/spec/unit/resources/permissive_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe 'selinux_permissive' do + step_into :selinux_permissive + platform 'centos' + + context 'when not set' do + recipe do + selinux_permissive 'test' do + action [:add, :delete] + end + end + + stubs_for_provider('selinux_permissive[test]') do |provider| + allow(provider).to receive_shell_out('semanage permissive -ln', stdout: 'other') + end + + # this is what actually checks that the fcontext was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_permissive[test]') do |provider| + # when not set, only add calls (-a) should happen + allow(provider).to receive_shell_out("semanage permissive -a 'test'") + end + + # needed to have the test run + it { is_expected.to add_selinux_permissive('test') } + it { is_expected.to delete_selinux_permissive('test') } + end + + context 'when set' do + recipe do + selinux_permissive 'test' do + action [:add, :delete] + end + end + + stubs_for_provider('selinux_permissive[test]') do |provider| + allow(provider).to receive_shell_out('semanage permissive -ln', stdout: 'test') + end + + # this is what actually checks that the fcontext was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_permissive[test]') do |provider| + # when not set, only delete calls (-d) should happen + allow(provider).to receive_shell_out("semanage permissive -d 'test'") + end + + # needed to have the test run + it { is_expected.to add_selinux_permissive('test') } + it { is_expected.to delete_selinux_permissive('test') } + end +end diff --git a/spec/unit/resources/port_spec.rb b/spec/unit/resources/port_spec.rb new file mode 100644 index 0000000..2eac20b --- /dev/null +++ b/spec/unit/resources/port_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe 'selinux_port' do + step_into :selinux_port + platform 'centos' + + stubs_for_provider('selinux_port[1234]') do |provider| + allow(provider).to receive_shell_out('getenforce', stdout: 'Permissive') + end + + recipe do + selinux_port '1234' do + protocol 'tcp' + secontext 'test_t' + action %i(manage add modify delete) + end + end + + context 'when not set' do + stubs_for_provider('selinux_port[1234]') do |provider| + allow(provider).to receive_shell_out(<<~CMD, stdout: '') + seinfo --portcon=1234 | grep 'portcon tcp' | \ + awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' + CMD + end + + # this is what actually checks that the port was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_port[1234]') do |provider| + # when not set, only add calls (-a) should happen + allow(provider).to receive_shell_out("semanage port -a -t 'test_t' -p tcp 1234") + end + + # needed to have the test run + it { is_expected.to manage_selinux_port('1234') } + it { is_expected.to add_selinux_port('1234') } + it { is_expected.to modify_selinux_port('1234') } + it { is_expected.to delete_selinux_port('1234') } + end + + context 'when set to incorrect value' do + stubs_for_provider('selinux_port[1234]') do |provider| + allow(provider).to receive_shell_out(<<~CMD, stdout: 'other_t') + seinfo --portcon=1234 | grep 'portcon tcp' | \ + awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' + CMD + end + + # this is what actually checks that the port was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_port[1234]') do |provider| + # when set incorrectly, only modify calls (-m) and delete calls (-d) should happen + allow(provider).to receive_shell_out("semanage port -m -t 'test_t' -p tcp 1234") + allow(provider).to receive_shell_out('semanage port -d -p tcp 1234') + end + + # needed to have the test run + it { is_expected.to manage_selinux_port('1234') } + it { is_expected.to add_selinux_port('1234') } + it { is_expected.to modify_selinux_port('1234') } + it { is_expected.to delete_selinux_port('1234') } + end + + context 'when set to correct value' do + stubs_for_provider('selinux_port[1234]') do |provider| + allow(provider).to receive_shell_out(<<~CMD, stdout: 'test_t') + seinfo --portcon=1234 | grep 'portcon tcp' | \ + awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' + CMD + end + + # this is what actually checks that the port was set correctly + # incorrect commands would not be stubbed and would throw error + stubs_for_provider('selinux_port[1234]') do |provider| + # when set correctly, only delete calls (-d) should happen + allow(provider).to receive_shell_out('semanage port -d -p tcp 1234') + end + + # needed to have the test run + it { is_expected.to manage_selinux_port('1234') } + it { is_expected.to add_selinux_port('1234') } + it { is_expected.to modify_selinux_port('1234') } + it { is_expected.to delete_selinux_port('1234') } + end +end diff --git a/spec/resources/state_spec.rb b/spec/unit/resources/state_spec.rb similarity index 78% rename from spec/resources/state_spec.rb rename to spec/unit/resources/state_spec.rb index de9fc50..3050cc1 100644 --- a/spec/resources/state_spec.rb +++ b/spec/unit/resources/state_spec.rb @@ -12,7 +12,7 @@ end stubs_for_provider('selinux_state[disabled]') do |provider| - allow(provider).to receive_shell_out('getenforce').and_return(double(error!: nil, stdout: 'Disabled')) + allow(provider).to receive_shell_out('getenforce', stdout: 'Disabled') end it 'Creates the selinux config file correctly' do @@ -29,7 +29,7 @@ end stubs_for_provider('selinux_state[permissive]') do |provider| - allow(provider).to receive_shell_out('getenforce').and_return(double(error!: nil, stdout: 'Permissive')) + allow(provider).to receive_shell_out('getenforce', stdout: 'Permissive') end it 'Creates the selinux config file correctly' do @@ -46,7 +46,7 @@ end stubs_for_provider('selinux_state[enforcing]') do |provider| - allow(provider).to receive_shell_out('getenforce').and_return(double(error!: nil, stdout: 'Enforcing')) + allow(provider).to receive_shell_out('getenforce', stdout: 'Enforcing') end it 'Creates the selinux config file correctly' do diff --git a/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.fc b/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.fc new file mode 100644 index 0000000..02533cd --- /dev/null +++ b/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.fc @@ -0,0 +1 @@ +/usr/bin/dirtest -- gen_context(system_u:object_r:dirtest_exec_t,s0) diff --git a/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.if b/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.if new file mode 100644 index 0000000..146b760 --- /dev/null +++ b/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.if @@ -0,0 +1,16 @@ +interface(`dirtest_domtrans',` + gen_requires(` + type dirtest_t, dirtest_exec_t; + ') + + domtrans_pattern($1,dirtest_exec_t,dirtest_t) +') + +interface(`dirtest_read_log',` + gen_requires(` + type dirtest_log_t; + ') + + logging_search_logs($1) + allow $1 dirtest_log_t:file read_file_perms; +') diff --git a/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.te b/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.te new file mode 100644 index 0000000..0e6d323 --- /dev/null +++ b/test/cookbooks/selinux_test/files/default/dirtest_module/dirtest.te @@ -0,0 +1,19 @@ +# Test module adapted from SELinux reference policy wiki +# https://github.com/SELinuxProject/refpolicy/wiki/GettingStarted + +policy_module(dirtest,0.0.1) + +type dirtest_t; +type dirtest_exec_t; +type dirtest_log_t; +type dirtest_tmp_t; + +domain_type(dirtest_t) +domain_entry_file(dirtest_t, dirtest_exec_t) +logging_log_file(dirtest_log_t) +files_tmp_file(dirtest_tmp_t) + +allow dirtest_t dirtest_log_t:file append_file_perms; +allow dirtest_t dirtest_tmp_t:file manage_file_perms; + +files_tmp_filetrans(dirtest_t,dirtest_tmp_t,file) diff --git a/test/cookbooks/selinux_test/metadata.rb b/test/cookbooks/selinux_test/metadata.rb index beb1f5a..a7234e9 100644 --- a/test/cookbooks/selinux_test/metadata.rb +++ b/test/cookbooks/selinux_test/metadata.rb @@ -2,3 +2,4 @@ version '0.0.1' depends 'selinux' +depends 'apt' diff --git a/test/cookbooks/selinux_test/recipes/fcontext.rb b/test/cookbooks/selinux_test/recipes/fcontext.rb new file mode 100644 index 0000000..e64f339 --- /dev/null +++ b/test/cookbooks/selinux_test/recipes/fcontext.rb @@ -0,0 +1,23 @@ +directory '/opt/selinux-test' + +%w( foo bar baz ).each do |f| + file "/opt/selinux-test/#{f}" +end + +directory '/opt/selinux-test/quux' + +# single file +selinux_fcontext '/opt/selinux-test/foo' do + secontext 'httpd_sys_content_t' +end + +# regex +selinux_fcontext '/opt/selinux-test/b.+' do + secontext 'boot_t' +end + +# file type +selinux_fcontext '/opt/selinux-test/.+' do + secontext 'etc_t' + file_type 'd' +end diff --git a/test/cookbooks/selinux_test/recipes/permissive_resource.rb b/test/cookbooks/selinux_test/recipes/permissive_resource.rb new file mode 100644 index 0000000..dc376ba --- /dev/null +++ b/test/cookbooks/selinux_test/recipes/permissive_resource.rb @@ -0,0 +1,3 @@ +selinux_permissive 'httpd_t' + +selinux_permissive 'user_t' diff --git a/test/cookbooks/selinux_test/recipes/port.rb b/test/cookbooks/selinux_test/recipes/port.rb new file mode 100644 index 0000000..15f8fa6 --- /dev/null +++ b/test/cookbooks/selinux_test/recipes/port.rb @@ -0,0 +1,11 @@ +%w(tcp udp).each do |prot| + selinux_port '29000' do + protocol prot + secontext 'http_port_t' + end +end + +selinux_port '29001' do + protocol 'tcp' + secontext 'ssh_port_t' +end diff --git a/test/integration/common/controls/install_control.rb b/test/integration/common/controls/install_control.rb new file mode 100644 index 0000000..5881076 --- /dev/null +++ b/test/integration/common/controls/install_control.rb @@ -0,0 +1,17 @@ +control 'install' do + title 'Verify SELinux packages are installed' + + pkgs = if os.debian? + %w(make policycoreutils selinux-basics selinux-policy-default selinux-policy-dev auditd setools) + elsif os.redhat? && os.release.to_i == 6 + %w(make policycoreutils selinux-policy selinux-policy-targeted libselinux-utils setools-console) + else + %w(make policycoreutils selinux-policy selinux-policy-targeted selinux-policy-devel libselinux-utils setools-console) + end + + pkgs.each do |pkg| + describe package(pkg) do + it { should be_installed } + end + end +end diff --git a/test/integration/common/inspec.yml b/test/integration/common/inspec.yml new file mode 100644 index 0000000..e0cad47 --- /dev/null +++ b/test/integration/common/inspec.yml @@ -0,0 +1,3 @@ +--- +name: common +title: Common tests for the SELinux cookbook diff --git a/test/integration/common/install_spec.rb b/test/integration/common/install_spec.rb deleted file mode 100644 index 3c0966e..0000000 --- a/test/integration/common/install_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -pkgs = if os.debian? - %w(make policycoreutils selinux-basics selinux-policy-default selinux-policy-dev auditd) - elsif os.redhat? && os.release.to_i == 6 - %w(make policycoreutils selinux-policy selinux-policy-targeted libselinux-utils) - else - %w(make policycoreutils selinux-policy selinux-policy-targeted selinux-policy-devel libselinux-utils) - end - -pkgs.each do |pkg| - describe package(pkg) do - it { should be_installed } - end -end diff --git a/test/integration/disabled/controls/disabled_control.rb b/test/integration/disabled/controls/disabled_control.rb new file mode 100644 index 0000000..08a307f --- /dev/null +++ b/test/integration/disabled/controls/disabled_control.rb @@ -0,0 +1,21 @@ +include_controls 'common' + +control 'disabled' do + title 'Verify SELinux is disabled' + + describe file('/etc/selinux/config') do + it { should exist } + it { should be_file } + its('owner') { should eq 'root' } + its('group') { should eq 'root' } + its('mode') { should cmp '0644' } + its('content') { should include 'SELINUX=disabled' } + end + + describe selinux do + it { should be_installed } + it { should be_disabled } + it { should_not be_enforcing } + it { should_not be_permissive } + end +end diff --git a/test/integration/disabled/inspec.yml b/test/integration/disabled/inspec.yml new file mode 100644 index 0000000..c557702 --- /dev/null +++ b/test/integration/disabled/inspec.yml @@ -0,0 +1,7 @@ +--- +name: disabled +title: Disabled suite tests for the SELinux cookbook + +depends: + - name: common + path: test/integration/common diff --git a/test/integration/disabled/state_spec.rb b/test/integration/disabled/state_spec.rb deleted file mode 100644 index 8f6895f..0000000 --- a/test/integration/disabled/state_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -describe file('/etc/selinux/config') do - it { should exist } - it { should be_file } - its('owner') { should eq 'root' } - its('group') { should eq 'root' } - its('mode') { should cmp '0644' } - its('content') { should include 'SELINUX=disabled' } -end - -describe selinux do - it { should be_installed } - it { should be_disabled } - it { should_not be_enforcing } - it { should_not be_permissive } -end diff --git a/test/integration/enforcing/boolean_spec.rb b/test/integration/enforcing/boolean_spec.rb deleted file mode 100644 index e89d2ba..0000000 --- a/test/integration/enforcing/boolean_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -describe selinux.booleans.where(name: 'ssh_keysign') do - it { should exist } - its('states') { should include 'on' } -end unless os.family.eql?('debian') - -describe selinux.booleans.where(name: 'httpd_enable_cgi') do - it { should exist } - its('states') { should include 'off' } -end - -describe selinux.booleans.where(name: 'ssh_sysadm_login') do - it { should exist } - its('states') { should include 'on' } -end - -describe selinux.booleans.where(name: 'squid_connect_any') do - it { should exist } - its('states') { should include 'off' } -end diff --git a/test/integration/enforcing/controls/boolean_control.rb b/test/integration/enforcing/controls/boolean_control.rb new file mode 100644 index 0000000..18ea591 --- /dev/null +++ b/test/integration/enforcing/controls/boolean_control.rb @@ -0,0 +1,23 @@ +control 'boolean' do + title 'Verify SELinux booleans are set correctly' + + describe selinux.booleans.where(name: 'ssh_keysign') do + it { should exist } + its('states') { should include 'on' } + end unless os.family.eql?('debian') + + describe selinux.booleans.where(name: 'httpd_enable_cgi') do + it { should exist } + its('states') { should include 'off' } + end + + describe selinux.booleans.where(name: 'ssh_sysadm_login') do + it { should exist } + its('states') { should include 'on' } + end + + describe selinux.booleans.where(name: 'squid_connect_any') do + it { should exist } + its('states') { should include 'off' } + end +end diff --git a/test/integration/enforcing/controls/enforcing_control.rb b/test/integration/enforcing/controls/enforcing_control.rb new file mode 100644 index 0000000..0639ca5 --- /dev/null +++ b/test/integration/enforcing/controls/enforcing_control.rb @@ -0,0 +1,26 @@ +include_controls 'common' + +control 'enforcing' do + title 'Verify that SELinux is enforcing' + + describe file('/etc/selinux/config') do + it { should exist } + it { should be_file } + its('owner') { should eq 'root' } + its('group') { should eq 'root' } + its('mode') { should cmp '0644' } + its('content') { should include 'SELINUX=enforcing' } + end + + describe selinux do + it { should be_installed } + it { should_not be_disabled } + it { should be_enforcing } + it { should_not be_permissive } + if os.family.eql?('debian') + its('policy') { should eq 'default' } + else + its('policy') { should eq 'targeted' } + end + end +end diff --git a/test/integration/enforcing/controls/module_control.rb b/test/integration/enforcing/controls/module_control.rb new file mode 100644 index 0000000..7339dae --- /dev/null +++ b/test/integration/enforcing/controls/module_control.rb @@ -0,0 +1,23 @@ +control 'module' do + title 'Verify that SELinux modules are installed correctly' + + describe selinux.modules.where(name: 'test') do + it { should_not exist } + it { should_not be_installed } + it { should_not be_enabled } + end + + if os.family.eql?('debian') + describe selinux.modules.where(name: 'moduleLoad') do + it { should exist } + it { should be_installed } + it { should be_enabled } + end + + describe selinux.modules.where(name: 'kitchenVerify') do + it { should exist } + it { should be_installed } + it { should be_enabled } + end + end +end diff --git a/test/integration/enforcing/inspec.yml b/test/integration/enforcing/inspec.yml new file mode 100644 index 0000000..6b9e6ca --- /dev/null +++ b/test/integration/enforcing/inspec.yml @@ -0,0 +1,7 @@ +--- +name: enforcing +title: Enforcing suite tests for the SELinux cookbook + +depends: + - name: common + path: test/integration/common diff --git a/test/integration/enforcing/module_spec.rb b/test/integration/enforcing/module_spec.rb deleted file mode 100644 index e091334..0000000 --- a/test/integration/enforcing/module_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -describe selinux.modules.where(name: 'test') do - it { should exist } - it { should be_installed } - it { should be_enabled } -end - -if os.family.eql?('debian') - describe selinux.modules.where(name: 'moduleLoad') do - it { should exist } - it { should be_installed } - it { should be_enabled } - end - - describe selinux.modules.where(name: 'kitchenVerify') do - it { should exist } - it { should be_installed } - it { should be_enabled } - end -end diff --git a/test/integration/enforcing/state_spec.rb b/test/integration/enforcing/state_spec.rb deleted file mode 100644 index 739dab0..0000000 --- a/test/integration/enforcing/state_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -describe file('/etc/selinux/config') do - it { should exist } - it { should be_file } - its('owner') { should eq 'root' } - its('group') { should eq 'root' } - its('mode') { should cmp '0644' } - its('content') { should include 'SELINUX=enforcing' } -end - -describe selinux do - it { should be_installed } - it { should_not be_disabled } - it { should be_enforcing } - it { should_not be_permissive } - if os.family.eql?('debian') - its('policy') { should eq 'default' } - else - its('policy') { should eq 'targeted' } - end -end diff --git a/test/integration/fcontext/controls/fcontext_control.rb b/test/integration/fcontext/controls/fcontext_control.rb new file mode 100644 index 0000000..5dc5033 --- /dev/null +++ b/test/integration/fcontext/controls/fcontext_control.rb @@ -0,0 +1,21 @@ +include_controls 'common' + +control 'fcontext' do + title 'Verify that SELinux file contexts are set correctly' + + describe file('/opt/selinux-test/foo') do + its('selinux_label') { should match 'httpd_sys_content_t' } + end + + describe file('/opt/selinux-test/bar') do + its('selinux_label') { should match 'boot_t' } + end + + describe file('/opt/selinux-test/baz') do + its('selinux_label') { should match 'boot_t' } + end + + describe file('/opt/selinux-test/quux') do + its('selinux_label') { should match 'etc_t' } + end +end diff --git a/test/integration/fcontext/inspec.yml b/test/integration/fcontext/inspec.yml new file mode 100644 index 0000000..87eb8b9 --- /dev/null +++ b/test/integration/fcontext/inspec.yml @@ -0,0 +1,7 @@ +--- +name: fcontext +title: Fcontext suite tests for the SELinux cookbook + +depends: + - name: common + path: test/integration/common diff --git a/test/integration/permissive/boolean_spec.rb b/test/integration/permissive/boolean_spec.rb deleted file mode 100644 index e89d2ba..0000000 --- a/test/integration/permissive/boolean_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -describe selinux.booleans.where(name: 'ssh_keysign') do - it { should exist } - its('states') { should include 'on' } -end unless os.family.eql?('debian') - -describe selinux.booleans.where(name: 'httpd_enable_cgi') do - it { should exist } - its('states') { should include 'off' } -end - -describe selinux.booleans.where(name: 'ssh_sysadm_login') do - it { should exist } - its('states') { should include 'on' } -end - -describe selinux.booleans.where(name: 'squid_connect_any') do - it { should exist } - its('states') { should include 'off' } -end diff --git a/test/integration/permissive/controls/boolean_control.rb b/test/integration/permissive/controls/boolean_control.rb new file mode 100644 index 0000000..18ea591 --- /dev/null +++ b/test/integration/permissive/controls/boolean_control.rb @@ -0,0 +1,23 @@ +control 'boolean' do + title 'Verify SELinux booleans are set correctly' + + describe selinux.booleans.where(name: 'ssh_keysign') do + it { should exist } + its('states') { should include 'on' } + end unless os.family.eql?('debian') + + describe selinux.booleans.where(name: 'httpd_enable_cgi') do + it { should exist } + its('states') { should include 'off' } + end + + describe selinux.booleans.where(name: 'ssh_sysadm_login') do + it { should exist } + its('states') { should include 'on' } + end + + describe selinux.booleans.where(name: 'squid_connect_any') do + it { should exist } + its('states') { should include 'off' } + end +end diff --git a/test/integration/permissive/controls/module_control.rb b/test/integration/permissive/controls/module_control.rb new file mode 100644 index 0000000..07175f1 --- /dev/null +++ b/test/integration/permissive/controls/module_control.rb @@ -0,0 +1,9 @@ +control 'module' do + title 'Verify that SELinux modules are installed correctly' + + describe selinux.modules.where(name: 'test') do + it { should exist } + it { should be_installed } + it { should be_enabled } + end +end diff --git a/test/integration/permissive/controls/permissive_control.rb b/test/integration/permissive/controls/permissive_control.rb new file mode 100644 index 0000000..9bdcb99 --- /dev/null +++ b/test/integration/permissive/controls/permissive_control.rb @@ -0,0 +1,22 @@ +include_controls 'common' + +control 'permissive' do + title 'Verify that SELinux is permissive' + + describe file('/etc/selinux/config') do + it { should exist } + it { should be_file } + its('owner') { should eq 'root' } + its('group') { should eq 'root' } + its('mode') { should cmp '0644' } + its('content') { should include 'SELINUX=permissive' } + end + + describe selinux do + it { should be_installed } + it { should_not be_disabled } + it { should_not be_enforcing } + it { should be_permissive } + its('policy') { should eq os.family.eql?('debian') ? 'default' : 'targeted' } + end +end diff --git a/test/integration/permissive/inspec.yml b/test/integration/permissive/inspec.yml new file mode 100644 index 0000000..bb8b131 --- /dev/null +++ b/test/integration/permissive/inspec.yml @@ -0,0 +1,7 @@ +--- +name: permissive +title: Permissive suite tests for the SELinux cookbook + +depends: + - name: common + path: test/integration/common diff --git a/test/integration/permissive/module_spec.rb b/test/integration/permissive/module_spec.rb deleted file mode 100644 index 5aeea9e..0000000 --- a/test/integration/permissive/module_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -describe selinux.modules.where(name: 'test') do - it { should_not exist } - it { should_not be_installed } - it { should_not be_enabled } -end diff --git a/test/integration/permissive/state_spec.rb b/test/integration/permissive/state_spec.rb deleted file mode 100644 index 7ed5c7e..0000000 --- a/test/integration/permissive/state_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -describe file('/etc/selinux/config') do - it { should exist } - it { should be_file } - its('owner') { should eq 'root' } - its('group') { should eq 'root' } - its('mode') { should cmp '0644' } - its('content') { should include 'SELINUX=permissive' } -end - -describe selinux do - it { should be_installed } - it { should_not be_disabled } - it { should_not be_enforcing } - it { should be_permissive } - its('policy') { should eq os.family.eql?('debian') ? 'default' : 'targeted' } -end diff --git a/test/integration/permissive_resource/controls/permissive_control.rb b/test/integration/permissive_resource/controls/permissive_control.rb new file mode 100644 index 0000000..724de36 --- /dev/null +++ b/test/integration/permissive_resource/controls/permissive_control.rb @@ -0,0 +1,10 @@ +include_controls 'common' + +control 'permissive' do + title 'Verify that SELinux permissive contexts are set correctly' + + describe command('semanage permissive -l') do + its('stdout') { should match 'httpd_t' } + its('stdout') { should match 'user_t' } + end +end diff --git a/test/integration/permissive_resource/inspec.yml b/test/integration/permissive_resource/inspec.yml new file mode 100644 index 0000000..e2006f5 --- /dev/null +++ b/test/integration/permissive_resource/inspec.yml @@ -0,0 +1,7 @@ +--- +name: permissive_resource +title: Permissive resource suite tests for the SELinux cookbook + +depends: + - name: common + path: test/integration/common diff --git a/test/integration/port/controls/port_control.rb b/test/integration/port/controls/port_control.rb new file mode 100644 index 0000000..834c13c --- /dev/null +++ b/test/integration/port/controls/port_control.rb @@ -0,0 +1,14 @@ +include_controls 'common' + +control 'port' do + title 'Verify that SELinux port contexts are set correctly' + + describe command('seinfo --portcon=29000') do + its('stdout') { should match 'portcon tcp 29000 system_u:object_r:http_port_t:s0' } + its('stdout') { should match 'portcon udp 29000 system_u:object_r:http_port_t:s0' } + end + + describe command('seinfo --portcon=29001') do + its('stdout') { should match 'portcon tcp 29001 system_u:object_r:ssh_port_t:s0' } + end +end diff --git a/test/integration/port/inspec.yml b/test/integration/port/inspec.yml new file mode 100644 index 0000000..6a286a9 --- /dev/null +++ b/test/integration/port/inspec.yml @@ -0,0 +1,7 @@ +--- +name: port +title: Port suite tests for the SELinux cookbook + +depends: + - name: common + path: test/integration/common