diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b5ad7b..e12b065 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,14 +24,18 @@ jobs: strategy: matrix: os: - - 'amazonlinux' - - 'amazonlinux-2' - 'centos-6' - 'centos-7' - 'centos-8' suite: - - 'default' - - 'setup' + - install + - boolean + - fcontext + - module + - module-directory + - module-remove + - permissive + - port fail-fast: false steps: diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index d88ffa9..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -AllCops: - Exclude: - - 'Dangerfile' diff --git a/CHANGELOG.md b/CHANGELOG.md index 08a5048..441c8ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ This file is used to changes made in each version of the selinux_policy cookbook. +## Unreleased + +- Refactor and clean up test recipes - [@detjensrobert](https://github.com/detjensrobert) +- Add Inspec tests for Kitchen test suites - [@detjensrobert](https://github.com/detjensrobert) +- Idempotency fixes for test recipes - [@detjensrobert](https://github.com/detjensrobert) +- Add ChefSpec tests - [@detjensrobert](https://github.com/detjensrobert) +- Refactor and clean up documentation - [@detjensrobert](https://github.com/detjensrobert) + ## 2.4.3 (2020-08-07) - Ship the correct license file since this cookbook was relicensed - [@tas50](https://github.com/tas50) diff --git a/README.md b/README.md index 73587db..6491b4d 100644 --- a/README.md +++ b/README.md @@ -3,207 +3,43 @@ [![Cookbook Version](https://img.shields.io/cookbook/v/selinux_policy.svg)](https://supermarket.chef.io/cookbooks/selinux_policy) [![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0) -This cookbook can be used to manage SELinux policies and components (rather than just enable / disable enforcing). I made it because I needed some SELinux settings done, and the `execute`s started to look annoying. +This cookbook can be used to manage SELinux policies and components. ## Requirements -Needs an SELinux policy active (so its values can be managed). Can work with a disabled SELinux system (see attribute `allow_disabled`), which will generate warnings and do nothing (but won't break the run). Also requires SELinux's management tools, namely `semanage`, `setsebool` and `getsebool`. Tools are installed by the `selinux_policy::install` recipe (for RHEL/Debian and the like). +Needs an active SELinux policy (so its values can be managed) and management tools, namely `semanage`, `setsebool` and `getsebool`. Tools are installed by the `selinux_policy_install` resource. + +If this cookbook needs to run on a node with SELinux disabled, set the `allow_disabled` property on resources to `true`, which will skip the resource and do nothing (but won't break the run). ### Chef Infra Client -- 13 or later +- 15 or later ### Platforms -- rhel -- fedora - -## Attributes - -These attributes affect the way all of the resource behave. - -- `node['selinux_policy']['allow_disabled']` - Whether to allow runs when SELinux is disabled. Will generate warnings, but the run won't fail. Defaults to `true`, set to `false` if you don't have any machines with disabled SELinux. +- CentOS 7+ +- Fedora latest ## Usage -- `selinux_policy::install` - Installs SELinux policy management tools - This cookbook's functionality is exposed via resources, so it should be called from a wrapper cookbook. Remember to add `depends 'selinux_policy'` to your `metadata.rb`. -### boolean - -Represents an SELinux [boolean](http://wiki.gentoo.org/wiki/SELinux/Tutorials/Using_SELinux_booleans). You can either `set` it, meaning it will be changed without persistence (it will revert to default in the next reboot), or `setpersist` it (default action), so it'll keep it value after rebooting. Using `setpersist` requires an active policy (so that the new value can be saved somewhere). - -Properties: - -- `name`: boolean's name. Defaults to resource name. -- `value`: Its new value (`true`/`false`). -- `force`: Use `setsebool` even if the current value agrees with the requested one. - -Example usage: - -```ruby -include_recipe 'selinux_policy::install' - -selinux_policy_boolean 'httpd_can_network_connect' do - value true - # Make sure nginx is started if this value was modified - notifies :start,'service[nginx]', :immediate -end -``` - -**Note**: Due to ruby interperting `0` as `true`, using `value 0` is unwise. - -### port - -Allows assigning a network port to a certain SELinux context. As explained [here](http://wiki.centos.org/HowTos/SELinux#head-ad837f60830442ae77a81aedd10c20305a811388), it can be useful for running Apache on a non-standard port. - -Actions: - -- `addormodify` (default): Assigns the port to the right context, whether it's already listed another context or not at all. -- `add`: Assigns the port to the right context it's if not listed (only uses `-a`). -- `modify`: Changes the port's context if it's already listed (only uses `-m`). -- `delete`: Removes the port's context if it's listed (uses `-d`). - -Properties: - -- `port`: The port in question, defaults to resource name. -- `protocol`: `tcp`/`udp`. -- `secontext`: The SELinux context to assign the port to. Unnecessary when using `delete`. - -Example usage: - -```ruby -include_recipe 'selinux_policy::install' - -# Allow nginx 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 -``` - -### module - -Manages SEModules - -Actions: - -- `fetch`: Prepares the module's files for compilation. Allow `remote_directory`-like behavior -- `compile`: Translates a module source directory into a `NAME.pp` file. Uses `make` logic for idempotence. -- `install`: Adds a compiled module (`pp`) to the current policy. Only installs if the module was modified this run, `force` is enabled or it's missing from the current policy. **Note:** I wish I could compare the existing module to the one generated, but the `extract` capability was only added in [Aug 15](https://github.com/SELinuxProject/selinux/commit/65c6325271b54d3de9c17352a57d469dfbd12729). I'll be happy to see a better idea. -- `deploy` (default): Runs `fetch`, `compile`, `install` in that order. -- `remove`: Removes a module. - -Properties: - -- `name`: The module name. Defaults to resource name. -- `directory`: Directory where module is stored. Defaults to a directory inside the Chef cache. -- `content`: The module content, can be extracted from `audit2allow -m NAME`. This can be used to create simple modules without using external files. -- `directory_source`: Copies files cookbook to the module directory (uses `remote_directory`). Allows keeping all of the module's source files in the cookbook. **Note:** You can pre-create the module directory and populate it in any other way you'd choose. -- `cookbook`: Modifies the source cookbook for the `remote_directory`. -- `force`: Installs the module even if it seems fine. Ruins idempotence but should help solve some weird cases. - -Example usage: - -```ruby -include_recipe 'selinux_policy::install' - -# Allow openvpn to write/delete in '/etc/openvpn' -selinux_policy_module 'openvpn-googleauthenticator' do - content <<-eos - module dy-openvpn-googleauthenticator 1.0; - - require { - type openvpn_t; - type openvpn_etc_t; - class file { write unlink }; - } - - - #============= openvpn_t ============== - allow openvpn_t openvpn_etc_t:file { write unlink }; - eos - action :deploy -end -``` - -### fcontext - -Allows managing the SELinux context of files. This can be used to grant SELinux-protected daemons access to additional / moved files. - -Actions: - -- `addormodify` (default): Assigns the file regexp to the right context, whether it's already listed another context or not at all. -- `add`: Assigns the file regexp to the right context it's if not listed (only uses -a). -- `modify`: Changes the file regexp context if it's already listed (only uses -m). -- `delete`: Removes the file regexp context if it's listed (uses -d). - -Properties: - -- `file_spec`: This is the file regexp in question, defaults to resource name. -- `secontext`: The SELinux context to assign the file regexp to. Not required for `:delete` -- `file_type`: Restrict the fcontext to specific file types. See the table below for an overview. See also for more info -- **a** All files -- **f** Regular files -- **d** Directory -- **c** Character device -- **b** Block device -- **s** Socket -- **l** Symbolic link -- **p** Namedpipe - -Example usage (see mysql cookbook for example daemons ): - -```ruby -include_recipe 'selinux_policy::install' - -# Allow http servers (nginx/apache) to modify moodle files -selinux_policy_fcontext '/var/www/moodle(/.*)?' do - secontext 'httpd_sys_rw_content_t' -end - -# Allow a custom mysql daemon to access its files. -{'mysqld_etc_t' => "/etc/mysql-#{service_name}(/.*)?", -'mysqld_etc_t' => "/etc/mysql-#{service_name}/my\.cnf", -'mysqld_log_t' => "/var/log/mysql-#{service_name}(/.*)?", -'mysqld_db_t' => "/opt/mysql_data_#{service_name}(/.*)?", -'mysqld_var_run_t' => "/var/run/mysql-#{service_name}(/.*)?", -'mysqld_initrc_exec_t' => "/etc/rc\.d/init\.d/mysql-#{service_name}"}.each do |sc, f| - selinux_policy_fcontext f do - secontext sc - end -end - -# Adapt a symbolic link -selinux_policy_fcontext '/var/www/symlink_to_webroot' do - secontext 'httpd_sys_rw_content_t' - filetype 'l' -end -``` - -### permissive - -Allows some types to misbehave without stopping them. Not as good as specific policies, but better than disabling SELinux entirely. - -Actions: - -- `add`: Adds a permissive, unless it's already added -- `delete`: Deletes a permissive if it's listed +## Recipes -Example usage: +- `selinux_policy::default`: Does nothing. +- `selinux_policy::install`: Calls `selinux_policy_install`. -```ruby -include_recipe 'selinux_policy::install' +## Resources -# Disable enforcement on Nginx -# As described on http://nginx.com/blog/nginx-se-linux-changes-upgrading-rhel-6-6/ +- [`selinux_policy_install`](documentation/resource_install.md) +- [`selinux_policy_boolean`](documentation/resource_boolean.md) +- [`selinux_policy_fcontext`](documentation/resource_fcontext.md) +- [`selinux_policy_module`](documentation/resource_module.md) +- [`selinux_policy_permissive`](documentation/resource_permissive.md) +- [`selinux_policy_port`](documentation/resource_port.md) -selinux_policy_permissive 'nginx' do - notifies :restart, 'service[nginx]' -end -``` +## Contributors -## Original Author +Original author: [Nitzan Raz](https://github.com/BackSlasher) ([backslasher](http://backslasher.net)) -[Nitzan Raz](https://github.com/BackSlasher) ([backslasher](http://backslasher.net)) \ No newline at end of file +Tests & documentation: [Robert Detjens](https://github.com/detjensrobert) diff --git a/documentation/resource_boolean.md b/documentation/resource_boolean.md new file mode 100644 index 0000000..d4efc86 --- /dev/null +++ b/documentation/resource_boolean.md @@ -0,0 +1,31 @@ +# selinux_policy_boolean + +Represents an SELinux [boolean](http://wiki.gentoo.org/wiki/SELinux/Tutorials/Using_SELinux_booleans). + +## Actions + +| Action | Description | +|---------------|--------------------------------------------------------------------------------| +| `:setpersist` | Default. The new value will persist across reboots. Requires an active policy. | +| `:set` | Sets new value without persistence. Reverts to the default value on reboot. | + +## Properties + +| Name | Type | Default value | Description | +|------------------|---------|---------------|-----------------------------------------------------------------------------------| +| `name` | String | Resource name | Name of the SELinux boolean | +| `value` | Boolean | | The value to set | +| `force` | Boolean | `false` | Whether to set even if the current value matches the new one. Breaks idempotency. | +| `allow_disabled` | Boolean | `true` | Whether to skip the resource if SELinux is not enabled. | + +## Examples + +```ruby +include_recipe 'selinux_policy::install' + +selinux_policy_boolean 'httpd_can_network_connect' do + value true + # Make sure nginx is started if this value was modified + notifies :start,'service[nginx]', :immediate +end +``` diff --git a/documentation/resource_fcontext.md b/documentation/resource_fcontext.md new file mode 100644 index 0000000..7a66718 --- /dev/null +++ b/documentation/resource_fcontext.md @@ -0,0 +1,62 @@ +# selinux_policy_fcontext + +Allows managing the SELinux context of files. This can be used to grant SELinux-protected daemons access to additional / moved files. + +## Actions + +| Action | Description | +|----------------|------------------------------------------------------------------------------| +| `:add` | Assigns the file to the right context if not listed (only uses `-a`). | +| `:addormodify` | Default. Assigns the file to the right context regardless of previous state. | +| `:delete` | Removes the file's context if listed (uses `-d`). | +| `:modify` | Changes the file's context if already listed (only uses `-m`). | +| `:relabel` | Restores context on the file or directory using `restorecon`. | + +## Properties + +| Name | Type | Default | Description | +|------------------|---------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `file_spec` | String, Regex | Resource name | Path or regular expression to files to modify. | +| `secontext` | String | | The SELinux context to assign the file to. | +| `file_type` | String | | Restrict the resource to only modifying specific file types. See list below or [https://en.wikipedia.org/wiki/Unix_file_types](https://en.wikipedia.org/wiki/Unix_file_types). | +| `allow_disabled` | Boolean | `true` | Whether to skip the resource if SELinux is not enabled. | + +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 +include_recipe 'selinux_policy::install' + +# Allow http servers (nginx/apache) to modify moodle files +selinux_policy_fcontext '/var/www/moodle(/.*)?' do + secontext 'httpd_sys_rw_content_t' +end + +# Allow a custom mysql daemon to access its files. +{'mysqld_etc_t' => "/etc/mysql-#{service_name}(/.*)?", +'mysqld_etc_t' => "/etc/mysql-#{service_name}/my\.cnf", +'mysqld_log_t' => "/var/log/mysql-#{service_name}(/.*)?", +'mysqld_db_t' => "/opt/mysql_data_#{service_name}(/.*)?", +'mysqld_var_run_t' => "/var/run/mysql-#{service_name}(/.*)?", +'mysqld_initrc_exec_t' => "/etc/rc\.d/init\.d/mysql-#{service_name}"}.each do |sc, f| + selinux_policy_fcontext f do + secontext sc + end +end + +# Adapt a symbolic link +selinux_policy_fcontext '/var/www/symlink_to_webroot' do + secontext 'httpd_sys_rw_content_t' + filetype 'l' +end +``` diff --git a/documentation/resource_install.md b/documentation/resource_install.md new file mode 100644 index 0000000..2e60446 --- /dev/null +++ b/documentation/resource_install.md @@ -0,0 +1,26 @@ +# selinux_policy_install + +Installs the required packages and tools to setup a working SELinux environment. A reboot may be required to fully enable SELinux. + +## Actions + +| Action | Description | +|------------|----------------------------------------| +| `:install` | Default. Installs the needed packages. | + +## Properties + +None. + +## Examples + +```ruby +selinux_policy_install 'example' do + notifies :reboot_now, 'reboot[selinux-reboot]', :immediately +end + +reboot 'selinux-reboot' do + action :nothing + reason 'Rebooting to enable SELinux.' +end +``` diff --git a/documentation/resource_module.md b/documentation/resource_module.md new file mode 100644 index 0000000..060c436 --- /dev/null +++ b/documentation/resource_module.md @@ -0,0 +1,53 @@ +# selinux_policy_module + +Manages compilation, installation, and removal of SELinux modules. + +## Actions + +| Action | Description | +|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `:deploy` | Default. Runs `fetch`, `compile`, `install` in that order. | +| `:fetch` | Prepares the module's files for compilation. Creates a file with the given content or uses `remote_directory` to fetch source files from a given cookbook. | +| `:compile` | Translates a module's source files into a `$NAME.pp` file. Uses `make` logic for idempotence. | +| `:install` | Adds a compiled module `.pp` file to the current policy. Only installs if the module was modified this run, `force` is set, or it's missing from the current policy. | +| `:remove` | Removes a module. | | | + +> **Note:** Ideally, this could compare the existing module to the new one, but the `extract` capability was only added in [this commit from Aug 15 2016](https://github.com/SELinuxProject/selinux/commit/65c6325271b54d3de9c17352a57d469dfbd12729). Until this change has rolled out to all supported platforms, the logic needs to be ugly :(. + +## Properties + +| Property | Type | Default value | Description | +|--------------------|---------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| `module_name` | String | Resource name | Name of the module to install/remove. | +| `directory` | String | Chef file cache | Directory where the module source files are stored before compilation. | +| `content` | String | | Content of the `.te` source file for the module. Useful for simple modules, e.g. from `audit2allow`. Conflicts with `directory_source`. | +| `directory_source` | String | | Copies a module directory from the cookbook using `remote_directory`. Useful for more complex modules with multiple files. Conflicts with `content`. | +| `cookbook` | String | | Source cookbook for `directory_source` / `remote_directory`. | +| `force` | Boolean | `false` | Forces (re)install of the module. Breaks idempotence. | +| `allow_disabled` | Boolean | `true` | Whether to skip the resource if SELinux is not enabled. | + +> **Note:** You can pre-create the module directory when using `directory_source` and populate it in any other way you'd choose. + +## Examples + +```ruby +include_recipe 'selinux_policy::install' + +# Allow openvpn to write/delete in '/etc/openvpn' +selinux_policy_module 'openvpn-googleauthenticator' do + content <<~EOM + module dy-openvpn-googleauthenticator 1.0; + + require { + type openvpn_t; + type openvpn_etc_t; + class file { write unlink }; + } + + + #============= openvpn_t ============== + allow openvpn_t openvpn_etc_t:file { write unlink }; + EOM + action :deploy +end +``` diff --git a/documentation/resource_permissive.md b/documentation/resource_permissive.md new file mode 100644 index 0000000..5c39f5a --- /dev/null +++ b/documentation/resource_permissive.md @@ -0,0 +1,28 @@ +# selinux_policy_permissive + +Allows some types to misbehave without stopping them. Not as good as specific policies, but better than disabling SELinux entirely. + +## 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. | +| `allow_disabled` | Boolean | `true` | Whether to skip the resource if SELinux is not enabled. | + +## Examples + +```ruby +include_recipe 'selinux_policy::install' + +# Disable enforcement on Apache +selinux_policy_permissive 'httpd_t' do + notifies :restart, 'service[httpd]' +end +``` diff --git a/documentation/resource_port.md b/documentation/resource_port.md new file mode 100644 index 0000000..d226c90 --- /dev/null +++ b/documentation/resource_port.md @@ -0,0 +1,33 @@ +# selinux_policy_port + +Allows assigning a network port to a certain SELinux context. As explained [here](https://wiki.centos.org/HowTos/SELinux#Allowing_Access_to_a_Port), it can be useful for running a webserver on a non-standard port. + +## Actions + +| Action | Description | +|----------------|------------------------------------------------------------------------------| +| `:addormodify` | Default. Assigns the port to the right context regardless of previous state. | +| `:add` | Assigns the port to the right context if not listed (only uses `-a`). | +| `:modify` | Changes the port's context if already listed (only uses `-m`). | +| `:delete` | Removes the port's context if listed (uses `-d`). | + +## Properties + +| Name | Type | Default value | 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. | +| `allow_disabled` | Boolean | `true` | Whether to skip the resource if SELinux is not enabled. | + +## Examples + +```ruby +include_recipe 'selinux_policy::install' + +# 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/kitchen.dokken.yml b/kitchen.dokken.yml index b2c9ee5..b2dd219 100644 --- a/kitchen.dokken.yml +++ b/kitchen.dokken.yml @@ -12,18 +12,10 @@ provisioner: product_name: chef product_version: <%= ENV['CHEF_VERSION'] || 'latest' %> install_strategy: once + enforce_idempotency: true + multiple_converge: 2 platforms: - - name: amazonlinux - driver: - image: dokken/amazonlinux - pid_one_command: /sbin/init - - - name: amazonlinux-2 - driver: - image: dokken/amazonlinux-2 - pid_one_command: /usr/lib/systemd/systemd - - name: centos-6 driver: image: dokken/centos-6 @@ -39,21 +31,6 @@ platforms: image: dokken/centos-8 pid_one_command: /usr/lib/systemd/systemd - - name: oraclelinux-6 - driver: - image: dokken/oraclelinux-6 - pid_one_command: /sbin/init - - - name: oraclelinux-7 - driver: - image: dokken/oraclelinux-7 - pid_one_command: /usr/lib/systemd/systemd - - - name: oraclelinux-8 - driver: - image: dokken/oraclelinux-8 - pid_one_command: /usr/lib/systemd/systemd - - name: fedora-latest driver: image: dokken/fedora-latest diff --git a/kitchen.yml b/kitchen.yml index 2e33cce..4d2bd62 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -6,32 +6,56 @@ provisioner: name: chef_zero deprecations_as_errors: true chef_license: accept-no-persist + enforce_idempotency: true + multiple_converge: 2 + # reboot tweaks: + max_retries: 3 + wait_for_retry: 90 + retry_on_exit_code: [35, 213] + client_rb: + client_fork: false platforms: - - name: amazonlinux - driver_config: - box: mvbcoding/awslinux - - name: amazonlinux-2 - name: centos-6 - name: centos-7 - name: centos-8 - name: fedora-latest +verifier: + name: inspec + suites: - - name: setup + - name: install run_list: - - recipe[test::setup] - - recipe[test::single_port] - - - name: default + - recipe[test::install] + - name: boolean + run_list: + - recipe[test::install] + - recipe[test::boolean] + - name: fcontext + run_list: + - recipe[test::install] + - recipe[test::fcontext] + - name: module run_list: - - recipe[test::setup] - - recipe[test::single_port] - - recipe[test::twice_port] - - recipe[test::range_port] - - recipe[test::bad_port] + - recipe[test::install] - recipe[test::module] + - name: module-directory + run_list: + - recipe[test::install] - recipe[test::module_directory] - - recipe[test::fcontext] - - recipe[test::fcontext_filetype] - - recipe[test::single_port_add_to_range] + - name: module-remove + run_list: + - recipe[test::install] + - recipe[test::module_remove] + provisioner: + enforce_idempotency: false + multiple_converge: 1 + - name: permissive + run_list: + - recipe[test::install] + - recipe[test::permissive] + - name: port + run_list: + - recipe[test::install] + - recipe[test::port] diff --git a/libraries/helpers.rb b/libraries/helpers.rb index cf163c5..2e76d2d 100644 --- a/libraries/helpers.rb +++ b/libraries/helpers.rb @@ -1,5 +1,5 @@ -class Chef - module SELinuxPolicy +module SELinuxPolicy + module Cookbook module Helpers require 'chef/mixin/shell_out' include Chef::Mixin::ShellOut @@ -20,16 +20,6 @@ def use_selinux(allow_disabled) return_val end - def sebool(new_resource, persist = false) - persist_string = persist ? '-P ' : '' - new_value = new_resource.value ? 'on' : 'off' - execute "selinux-setbool-#{new_resource.name}-#{new_value}" do - command "#{setsebool_cmd} #{persist_string} #{new_resource.name} #{new_value}" - not_if "#{getsebool_cmd} #{new_resource.name} | grep '#{new_value}$' >/dev/null" unless new_resource.force - only_if { use_selinux(new_resource.allow_disabled) } - end - end - def module_defined(name) "#{semodule_cmd} -l | grep -w '^#{name}'" end @@ -48,10 +38,6 @@ def port_defined(protocol, port, label = nil) "#{base_command} | #{grep}" end - def validate_port(port) - raise ArgumentError, "port value: #{port} is invalid." unless port.to_s =~ /^\d+$/ - end - def fcontext_defined(file_spec, file_type, label = nil) file_hash = { 'a' => 'all files', @@ -70,7 +56,7 @@ def fcontext_defined(file_spec, file_type, label = nil) def semanage_options(file_type) # Set options for file_type - if node['platform_family'].include?('rhel') && Chef::VersionConstraint.new('< 7.0').include?(node['platform_version']) + if platform_family?('rhel') && node['platform_version'].to_i == 6 case file_type when 'a' then '-f ""' when 'f' then '-f --' @@ -81,28 +67,36 @@ def semanage_options(file_type) end end - require 'chef/mixin/which' - include Chef::Mixin::Which + def cmd_path(command) + if platform_family?('rhel') && node['platform_version'].to_i == 6 + "/usr/sbin/#{command}" + else + "/sbin/#{command}" + end + end def setsebool_cmd - @setsebool_cmd ||= which('setsebool') + cmd_path('setsebool') end def getsebool_cmd - @getsebool_cmd ||= which('getsebool') + cmd_path('getsebool') end def getenforce_cmd - @getenforce_cmd ||= which('getenforce') + cmd_path('getenforce') end def semanage_cmd - @semanage_cmd ||= which('semanage') + cmd_path('semanage') end def semodule_cmd - @semodule_cmd ||= which('semodule') + cmd_path('semodule') end end end end + +Chef::Recipe.include ::SELinuxPolicy::Cookbook::Helpers +Chef::Resource.include ::SELinuxPolicy::Cookbook::Helpers diff --git a/metadata.rb b/metadata.rb index b715015..bcf0959 100644 --- a/metadata.rb +++ b/metadata.rb @@ -11,6 +11,3 @@ supports 'redhat' supports 'centos' supports 'fedora' -supports 'ubuntu' -supports 'debian' -supports 'amazon' diff --git a/resources/boolean.rb b/resources/boolean.rb index 3fd359c..5ce0a19 100644 --- a/resources/boolean.rb +++ b/resources/boolean.rb @@ -4,16 +4,25 @@ property :force, [true, false], default: false property :allow_disabled, [true, false], default: true +action_class do + def sebool(persist = false) + persist_string = persist ? '-P' : '' + new_value = new_resource.value ? 'on' : 'off' + + execute "selinux-setbool-#{new_resource.name}-#{new_value}" do + command "#{setsebool_cmd} #{persist_string} #{new_resource.name} #{new_value}" + not_if "#{getsebool_cmd} #{new_resource.name} | grep -q '#{new_value}$'" unless new_resource.force + only_if { use_selinux(new_resource.allow_disabled) } + end + end +end + # Set and persist action :setpersist do - sebool(new_resource, true) + sebool(true) end # Set for now, without persisting action :set do - sebool(new_resource, false) -end - -action_class do - include Chef::SELinuxPolicy::Helpers + sebool(false) end diff --git a/resources/fcontext.rb b/resources/fcontext.rb index 596dc18..1e56cd3 100644 --- a/resources/fcontext.rb +++ b/resources/fcontext.rb @@ -14,24 +14,23 @@ # Run restorecon to fix label # https://github.com/sous-chefs/selinux_policy/pull/72#issuecomment-338718721 action :relabel do - converge_by 'relabel' do - spec = new_resource.file_spec - escaped = Regexp.escape spec + spec = new_resource.file_spec + escaped = Regexp.escape spec - common = - if spec == escaped - spec - else - index = spec.size.times { |i| break i if spec[i] != escaped[i] } - ::File.dirname spec[0...index] - end + # 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 - # Just in case the spec is very weird... - common = '/' if common[0] != '/' + # 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 2>/dev/null | xargs -0 restorecon -iRv") - end + execute 'selinux-fcontext-relabel' do + command "find #{common.shellescape} -ignore_readdir_race -regextype posix-egrep -regex #{spec.shellescape} -prune -print0 2>/dev/null | xargs -0 restorecon -iRv" + only_if { ::File.exist? common } end end @@ -58,14 +57,9 @@ action :modify do execute "selinux-fcontext-#{new_resource.secontext}-modify" do command "#{semanage_cmd} fcontext -m #{semanage_options(new_resource.file_type)} -t #{new_resource.secontext} '#{new_resource.file_spec}'" - only_if { use_selinux(new_resource.allow_disabled) } only_if fcontext_defined(new_resource.file_spec, new_resource.file_type) not_if fcontext_defined(new_resource.file_spec, new_resource.file_type, new_resource.secontext) + only_if { use_selinux(new_resource.allow_disabled) } notifies :relabel, new_resource, :immediately end end - -action_class do - include Chef::SELinuxPolicy::Helpers - include Chef::Mixin::Which -end diff --git a/resources/install.rb b/resources/install.rb index 2d4a181..a4eb109 100644 --- a/resources/install.rb +++ b/resources/install.rb @@ -1,17 +1,7 @@ -property :allow_disabled, [true, false], default: true - action :install do case node['platform_family'] when 'debian' - raise 'Install SELinux manually on Ubuntu. See https://wiki.ubuntu.com/SELinux' if platform?('ubuntu') - - execute 'selinux-activate' do - action :nothing - end - - package %w(selinux-policy-default selinux-basics auditd) do - notifies :run, 'execute[selinux-activate]', :immediately - end + raise 'Debian / Ubuntu are not supported by this cookbook' when 'rhel' case node['platform_version'].to_i @@ -22,11 +12,13 @@ when 8 package %w(policycoreutils-python-utils selinux-policy-devel setools-console make) else - raise 'Unknown version of RHEL/derivative, cannot determine required package names' + raise 'Unknown version of RHEL/derivative, cannot determine required packages' end + when 'fedora' - package %w(policycoreutils-python selinux-policy-devel setools-console make) + package %w(policycoreutils selinux-policy-devel setools-console make) + else - raise 'Unknown distro, cannot determine required package names' + raise 'Unknown / unsupported platform, cannot determine required packages' end end diff --git a/resources/module.rb b/resources/module.rb index 532f12d..fa80147 100644 --- a/resources/module.rb +++ b/resources/module.rb @@ -21,7 +21,7 @@ only_if { use_selinux(new_resource.allow_disabled) } end - raise 'dont specify both directory_source and content' if new_resource.directory_source && new_resource.content + raise 'Do not specify both directory_source and content' if new_resource.directory_source && new_resource.content if new_resource.directory_source remote_directory new_resource.directory do @@ -43,7 +43,7 @@ make_command = "/usr/bin/make -f /usr/share/selinux/devel/Makefile #{new_resource.module_name}.pp" execute "semodule-compile-#{new_resource.module_name}" do command make_command - not_if "#{make_command} -q", cwd: new_resource.directory # $? = 1 means make wants to execute http://www.gnu.org/software/make/manual/html_node/Running.html + not_if "#{make_command} -q", cwd: new_resource.directory # $?==0 means makeis up to date -- see http://www.gnu.org/software/make/manual/html_node/Running.html only_if { use_selinux(new_resource.allow_disabled) } cwd new_resource.directory end @@ -69,7 +69,3 @@ only_if { use_selinux(new_resource.allow_disabled) } end end - -action_class do - include Chef::SELinuxPolicy::Helpers -end diff --git a/resources/permissive.rb b/resources/permissive.rb index d6bf92d..27e97cb 100644 --- a/resources/permissive.rb +++ b/resources/permissive.rb @@ -1,25 +1,22 @@ # a resource for managing selinux permissive contexts +property :context, name_property: true property :allow_disabled, [true, false], default: true -# Create if doesn't exist, do not touch if port is already registered (even under different type) +# Create if doesn't exist, do not touch if permissive is already registered (even under different type) action :add do - execute "selinux-permissive-#{new_resource.name}-add" do - command "#{semanage_cmd} permissive -a '#{new_resource.name}'" - not_if "#{semanage_cmd} permissive -l | grep '^#{new_resource.name}$'" + execute "selinux-permissive-#{new_resource.context}-add" do + command "#{semanage_cmd} permissive -a '#{new_resource.context}'" + not_if "#{semanage_cmd} permissive -l | grep -Fxq '#{new_resource.context}'" only_if { use_selinux(new_resource.allow_disabled) } end end # Delete if exists action :delete do - execute "selinux-port-#{new_resource.name}-delete" do - command "#{semanage_cmd} permissive -d '#{new_resource.name}'" - not_if "#{semanage_cmd} permissive -l | grep '^#{new_resource.name}$'" + execute "selinux-permissive-#{new_resource.context}-delete" do + command "#{semanage_cmd} permissive -d '#{new_resource.context}'" + only_if "#{semanage_cmd} permissive -l | grep -Fxq '#{new_resource.context}'" only_if { use_selinux(new_resource.allow_disabled) } end end - -action_class do - include Chef::SELinuxPolicy::Helpers -end diff --git a/resources/port.rb b/resources/port.rb index 87b76fb..fd14565 100644 --- a/resources/port.rb +++ b/resources/port.rb @@ -1,41 +1,27 @@ # Manages a port assignment in SELinux # See http://docs.fedoraproject.org/en-US/Fedora/13/html/SELinux_FAQ/index.html#id3715134 -property :port, [Integer, String], name_property: true -property :protocol, String, equal_to: %w(tcp udp) -property :secontext, String +property :port, [Integer, String], name_property: true, regex: /^\d+$/ +property :protocol, String, equal_to: %w(tcp udp), required: %i(addormodify add modify) +property :secontext, String, required: %i(addormodify add modify) property :allow_disabled, [true, false], default: true action :addormodify do - # TODO: We can be a bit more clever here, and try to detect if it's already - # there then modify - # Try to add new port - run_action(:add) - # Try to modify existing port - run_action(:modify) + # TODO: We can be a bit more clever here, and try to detect if it's already there then modify + run_action(:add) # Try to add new port + run_action(:modify) # Try to modify existing port end # Create if doesn't exist, do not touch if port is already registered (even under different type) action :add do - validate_port(new_resource.port) execute "selinux-port-#{new_resource.port}-add" do command "#{semanage_cmd} port -a -t #{new_resource.secontext} -p #{new_resource.protocol} #{new_resource.port}" - not_if port_defined(new_resource.protocol, new_resource.port, new_resource.secontext) not_if port_defined(new_resource.protocol, new_resource.port) only_if { use_selinux(new_resource.allow_disabled) } end end -# Delete if exists -action :delete do - validate_port(new_resource.port) - execute "selinux-port-#{new_resource.port}-delete" do - command "#{semanage_cmd} port -d -p #{new_resource.protocol} #{new_resource.port}" - only_if port_defined(new_resource.protocol, new_resource.port) - only_if { use_selinux(new_resource.allow_disabled) } - end -end - +# Only modify port if it exists & doesn't have the correct context already action :modify do execute "selinux-port-#{new_resource.port}-modify" do command "#{semanage_cmd} port -m -t #{new_resource.secontext} -p #{new_resource.protocol} #{new_resource.port}" @@ -45,6 +31,11 @@ end end -action_class do - include Chef::SELinuxPolicy::Helpers +# Delete if exists +action :delete do + execute "selinux-port-#{new_resource.port}-delete" do + command "#{semanage_cmd} port -d -p #{new_resource.protocol} #{new_resource.port}" + only_if port_defined(new_resource.protocol, new_resource.port) + only_if { use_selinux(new_resource.allow_disabled) } + end end diff --git a/spec/boolean_spec.rb b/spec/boolean_spec.rb deleted file mode 100644 index fc3c379..0000000 --- a/spec/boolean_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'spec_helper' - -describe 'selinux_policy boolean' do - before do - allow_any_instance_of(Chef::Resource).to receive(:use_selinux).and_return(true) - end - let :chef_run do - ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_boolean']).converge_dsl('selinux_policy') do - selinux_policy_boolean 'httpd_can_network_connect_db' do - value true - end - selinux_policy_boolean 'nagios_run_sudo' do - value false - end - end - end - - describe 'SetAndPersist' do - it 'when value does not match' do - stub_command("getsebool httpd_can_network_connect_db | grep 'on$' >/dev/null").and_return(false) - stub_command("getsebool nagios_run_sudo | grep 'off$' >/dev/null").and_return(false) - expect(chef_run).to run_execute('selinux-setbool-httpd_can_network_connect_db-on') - expect(chef_run).to run_execute('selinux-setbool-nagios_run_sudo-off') - end - - it 'when value does match' do - stub_command("getsebool httpd_can_network_connect_db | grep 'on$' >/dev/null").and_return(true) - stub_command("getsebool nagios_run_sudo | grep 'off$' >/dev/null").and_return(true) - expect(chef_run).not_to run_execute('selinux-setbool-httpd_can_network_connect_db-on') - expect(chef_run).not_to run_execute('selinux-setbool-nagios_run_sudo-off') - end - end -end diff --git a/spec/fcontext_spec.rb b/spec/fcontext_spec.rb deleted file mode 100644 index 32dec92..0000000 --- a/spec/fcontext_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'spec_helper' - -describe 'selinux_policy fcontext' do - before do - allow_any_instance_of(Chef::Resource).to receive(:use_selinux).and_return(true) - end - let :chef_run do - ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_fcontext']).converge_dsl('selinux_policy') do - selinux_policy_fcontext '/tmp/test' do - secontext 'http_dir_t' - end - end - end - - describe 'AddOrModify' do - it 'creates when none' do - stub_command("semanage fcontext -l | grep -qP '^/tmp/test\\s+all\\ files\\s+'").and_return(false) - stub_command("semanage fcontext -l | grep -qP '^/tmp/test\\s+all\\ files\\s+system_u:object_r:http_dir_t:s0\\s*$'").and_return(false) - expect(chef_run).to run_execute('selinux-fcontext-http_dir_t-add') - expect(chef_run).not_to run_execute('selinux-fcontext-http_dir_t-modify') - end - it 'modifies when exists and mismatch' do - stub_command("semanage fcontext -l | grep -qP '^/tmp/test\\s+all\\ files\\s+'").and_return(true) - stub_command("semanage fcontext -l | grep -qP '^/tmp/test\\s+all\\ files\\s+system_u:object_r:http_dir_t:s0\\s*$'").and_return(false) - expect(chef_run).not_to run_execute('selinux-fcontext-http_dir_t-add') - expect(chef_run).to run_execute('selinux-fcontext-http_dir_t-modify') - end - it 'does nothing when match' do - stub_command("semanage fcontext -l | grep -qP '^/tmp/test\\s+all\\ files\\s+'").and_return(true) - stub_command("semanage fcontext -l | grep -qP '^/tmp/test\\s+all\\ files\\s+system_u:object_r:http_dir_t:s0\\s*$'").and_return(true) - expect(chef_run).not_to run_execute('selinux-fcontext-http_dir_t-add') - expect(chef_run).not_to run_execute('selinux-fcontext-http_dir_t-modify') - end - end - - describe 'Add' do - # it 'creates when none' - # it 'does nothing when exists' - end - - describe 'Modify' do - # it 'does nothing when none' - # it 'modifies when exists and mismatch' - # it 'does nothing when match' - end - - describe 'Delete' do - # it 'does nothing when none' - # it 'removes when match' - end -end diff --git a/spec/module_spec.rb b/spec/module_spec.rb deleted file mode 100644 index e8863cb..0000000 --- a/spec/module_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'spec_helper' - -describe 'selinux_policy module' do - before do - allow_any_instance_of(Chef::Resource).to receive(:use_selinux).and_return(true) - end - def chef_run_module(*actions) - ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_module']).converge_dsl('selinux_policy') do - selinux_policy_module 'testy' do - action actions - allow_disabled true - content <<-eos - policy_module(testy, 1.0.0) - type testy_t; - eos - end - end - end - describe 'fetch' do - describe 'disallows both source_directory and content' do - let :chef_run do - ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_module']).converge_dsl('selinux_policy') do - selinux_policy_module 'testy' do - action :fetch - allow_disabled false - content <<-eos - policy_module(testy, 1.0.0) - type testy_t; - eos - directory_source 'lolzaur' - end - end - end - it 'works' do - expect { chef_run }.to raise_error(Exception) - end - end - describe 'source_directory' do - let :chef_run do - ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_module']).converge_dsl('selinux_policy') do - selinux_policy_module 'testy' do - allow_disabled false - action :fetch - directory_source 'lolzaur' - end - end - end - it 'works' do - expect { chef_run }.not_to raise_error(Exception) - end - end - describe 'content' do - let :chef_run do - ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_module']).converge_dsl('selinux_policy') do - selinux_policy_module 'testy' do - action :fetch - allow_disabled false - content <<-eos - policy_module(testy, 1.0.0) - type testy_t; - eos - end - end - end - it 'works' do - expect { chef_run }.not_to raise_error(Exception) - end - end - end - describe 'compile' do - let(:chef_run) { chef_run_module(:compile) } - it 'acts when needed' do - stub_command('/usr/bin/make -f /usr/share/selinux/devel/Makefile testy.pp -q').and_return(false) - expect(chef_run).to run_execute('semodule-compile-testy') - end - it 'does nothing when not needed' do - stub_command('/usr/bin/make -f /usr/share/selinux/devel/Makefile testy.pp -q').and_return(true) - expect(chef_run).not_to run_execute('semodule-compile-testy') - end - end - describe 'install' do - let(:chef_run) { chef_run_module(:install) } - it 'acts when needed' do - stub_command("false || ! (semodule -l | grep -w '^testy') ").and_return(true) - expect(chef_run).to run_execute('semodule-install-testy') - end - it 'does nothing when not needed' do - stub_command("false || ! (semodule -l | grep -w '^testy') ").and_return(false) - expect(chef_run).not_to run_execute('semodule-install-testy') - end - end - describe 'Remove' do - let :chef_run do - ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_module']).converge_dsl('selinux_policy') do - selinux_policy_module 'testy' do - allow_disabled true - action :remove - end - end - end - it 'does nothing when none' do - stub_command("semodule -l | grep -w '^testy'").and_return(false) - expect(chef_run).not_to run_execute('semodule-remove-testy') - end - it 'removes when match' do - stub_command("semodule -l | grep -w '^testy'").and_return(true) - expect(chef_run).to run_execute('semodule-remove-testy') - end - end -end diff --git a/spec/port_spec.rb b/spec/port_spec.rb deleted file mode 100644 index 9ee33fe..0000000 --- a/spec/port_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'spec_helper' - -describe 'selinux_policy port' do - before do - allow_any_instance_of(Chef::Resource).to receive(:use_selinux).and_return(true) - end - describe 'single port' do - describe 'AddOrModify' do - let(:chef_run) do - runner = ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_port']) - runner.converge('test::single_port') - end - it 'defines a single port' do - stub_command("seinfo --portcon=1080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -q ^").and_return(false) - stub_command("seinfo --portcon=1080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -P 'http_port_t'").and_return(false) - expect(chef_run).to run_execute('selinux-port-1080-add') - expect(chef_run).not_to run_execute('selinux-port-1080-modify') - end - it 'redefines the port, same secontext' do - stub_command("seinfo --portcon=1080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -q ^").and_return(true) - stub_command("seinfo --portcon=1080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -P 'http_port_t'").and_return(true) - expect(chef_run).not_to run_execute('selinux-port-1080-add') - expect(chef_run).not_to run_execute('selinux-port-1080-modify') - end - it 'avoids redefining the port, different secontext' do - stub_command("seinfo --portcon=1080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -q ^").and_return(true) - stub_command("seinfo --portcon=1080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -P 'http_port_t'").and_return(false) - expect(chef_run).not_to run_execute('selinux-port-1080-add') - expect(chef_run).to run_execute('selinux-port-1080-modify') - end - end - describe 'default_AddOrModify' do - let(:chef_run) do - runner = ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_port']) - runner.converge('test::single_default_port') - end - it 'defines a single port' do - stub_command('[ "$(getenforce)" = "Enforcing" ]').and_return(true) - stub_command("seinfo --portcon=10080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -q ^").and_return(false) - stub_command("seinfo --portcon=10080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -P 'http_port_t'").and_return(false) - expect(chef_run).to run_execute('selinux-port-10080-add') - expect(chef_run).not_to run_execute('selinux-port-10080-modify') - end - it 'avoids redefines the port, same secontext' do - stub_command('[ "$(getenforce)" = "Enforcing" ]').and_return(true) - stub_command("seinfo --portcon=10080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -q ^").and_return(true) - stub_command("seinfo --portcon=10080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -P 'http_port_t'").and_return(true) - expect(chef_run).not_to run_execute('selinux-port-10080-add') - expect(chef_run).not_to run_execute('selinux-port-10080-modify') - end - it 'redefining the port, different secontext' do - stub_command('[ "$(getenforce)" = "Enforcing" ]').and_return(true) - stub_command("seinfo --portcon=10080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -q ^").and_return(true) - stub_command("seinfo --portcon=10080 | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -P 'http_port_t'").and_return(false) - expect(chef_run).not_to run_execute('selinux-port-10080-add') - expect(chef_run).to run_execute('selinux-port-10080-modify') - end - end - # TODO: 'Add': - # when port is OK - # when port is different label - # when port doesn't exist - # TODO 'Modify': - # when port is OK - # when said port exists - # when port doesn't exist - # TODO 'Delete' - # when port is not there (OK) - # when port exists - end - - describe 'range of ports' do - let(:chef_run) do - runner = ChefSpec::SoloRunner.new(platform: 'centos', version: '7', step_into: ['selinux_policy_port']) - Chef::Config[:cookbook_path] << './test/cookbooks' - runner.converge('test::range_port') - end - # TODO: complete - # it 'defines a port' do - # expect(chef_run).to run_execute('selinux-port-1901-addormodify') - # end - # it 'correctly detects a port in range' do - # expect(chef_run).to run_execute('selinux-port-1901-addormodify') - # end - # TODO testing: - # The matching script is not actually being tested here, so we can only check how the script responds on "match" and "no match" - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b08b44d..816b997 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,37 +1,15 @@ require 'chefspec' require 'chefspec/berkshelf' -module ChefSpec - class SoloRunner - def converge_dsl(*recipes, &block) - cookbook_name = 'imaginary' - recipe_name = 'temp' - converge(*recipes) do - recipe = Chef::Recipe.new(cookbook_name, recipe_name, @run_context) - recipe.instance_eval(&block) - end - end - end -end - -# Stub this for the tests -class Chef - module Mixin - module Which - def which(*cmds) - cmds.first - end - end - end -end +require_relative '../libraries/helpers' RSpec.configure do |config| config.color = true - config.tty = true config.formatter = :documentation - config.filter_run focus: true - config.run_all_when_everything_filtered = true - config.expect_with :rspec do |c| - c.syntax = :expect +end + +shared_context 'selinux enabled' do + before do + allow_any_instance_of(SELinuxPolicy::Cookbook::Helpers).to receive(:use_selinux).and_return(true) end end diff --git a/spec/unit/libraries/helpers_spec.rb b/spec/unit/libraries/helpers_spec.rb new file mode 100644 index 0000000..757494f --- /dev/null +++ b/spec/unit/libraries/helpers_spec.rb @@ -0,0 +1,183 @@ +require 'spec_helper' + +describe SELinuxPolicy::Cookbook::Helpers do + class DummyClass < Chef::Node + include SELinuxPolicy::Cookbook::Helpers + end + + subject { DummyClass.new } + + # manually mock fauxhai data since that cannot be used without a recipe + before do + allow(subject).to receive(:[]).with(:platform_family).and_return('rhel') + allow(subject).to receive(:[]).with('platform_version').and_return('8') + end + + describe '#use_selinux' do + let(:shellout) do + double(run_command: nil, error!: nil, stdout: '', stderr: '', exitstatus: 0, live_stream: '') + end + + before { allow(Mixlib::ShellOut).to receive(:new).and_return(shellout) } + + context 'when selinux is permissive' do + before { allow(shellout).to receive('stdout').and_return('Permissive') } + + it 'returns the correct value' do + expect(subject.use_selinux(true)).to eq true + end + it 'returns the correct value when not allowing disabled' do + expect(subject.use_selinux(false)).to eq true + end + end + + context 'when selinux is enforcing' do + before { allow(shellout).to receive('stdout').and_return('Enforcing') } + + it 'returns the correct value' do + expect(subject.use_selinux(true)).to eq true + end + it 'returns the correct value when not allowing disabled' do + expect(subject.use_selinux(false)).to eq true + end + end + + context 'when selinux is disabled' do + before { allow(shellout).to receive('stdout').and_return('Disabled') } + + it 'returns the correct value' do + expect(subject.use_selinux(true)).to eq false + end + it 'returns the correct value when not allowing disabled' do + expect(subject.use_selinux(false)).to eq true + end + end + + context 'selinux is not installed' do + before { allow(Mixlib::ShellOut).to receive(:new).and_raise(Errno::ENOENT) } + + it 'returns the correct value' do + expect(subject.use_selinux(true)).to eq false + end + it 'returns the correct value when not allowing disabled' do + expect(subject.use_selinux(false)).to eq true + end + end + end + + describe '#module_defined' do + before { allow(subject).to receive(:semodule_cmd).and_return('semodule') } + + context 'with testname' do + it 'returns the correct command string' do + expect(subject.module_defined('testname')).to eq "semodule -l | grep -w '^testname'" + end + end + end + + describe '#shell_boolean' do + context 'with truthy expression' do + it 'returns the correct bool string' do + expect(subject.shell_boolean(true)).to eq 'true' + end + end + + context 'with falsy expression' do + it 'returns the correct bool string' do + expect(subject.shell_boolean(false)).to eq 'false' + end + end + end + + describe '#port_defined' do + awk_cmd = "awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}'" + + context 'without label' do + it 'returns the correct command string' do + expect(subject.port_defined('tcp', '6969')).to eq "seinfo --portcon=6969 | grep 'portcon tcp' | #{awk_cmd} | grep -q ^" + end + end + + context 'with label string' do + it 'returns the correct command string' do + expect(subject.port_defined('udp', '6970', 'testlabel')).to eq "seinfo --portcon=6970 | grep 'portcon udp' | #{awk_cmd} | grep -P 'testlabel'" + end + end + + context 'with label regex' do + it 'returns the correct command string' do + expect(subject.port_defined('tcp', '6971', 'escape.+test')).to eq "seinfo --portcon=6971 | grep 'portcon tcp' | #{awk_cmd} | grep -P 'escape\\.\\+test'" + end + end + end + + describe '#fcontext_defined' do + before { allow(subject).to receive(:semanage_cmd).and_return('semanage') } + + context 'without label' do + it 'returns the correct command string' do + expect(subject.fcontext_defined('test/file.path', 'f')).to eq "semanage fcontext -l | grep -qP '^test/file\\.path\\s+regular\\ file\\s+'" + end + end + + context 'with label' do + it 'returns the correct command string' do + expect(subject.fcontext_defined('test/file.path', 'a', 'test_label_t')).to eq "semanage fcontext -l | grep -qP '^test/file\\.path\\s+all\\ files\\s+system_u:object_r:test_label_t:s0\\s*$'" + end + end + end + + describe '#semanage_options' do + %w(a f d c b s l p).each do |type| + context "with file type #{type}" do + it 'returns the correct string' do + expect(subject.semanage_options(type)).to eq "-f #{type}" + end + end + end + + context 'on centos 6' do + before { allow(subject).to receive(:[]).with('platform_version').and_return('6') } + + context 'with file type a' do + it 'returns the correct string' do + expect(subject.semanage_options('a')).to eq '-f ""' + end + end + context 'with file type f' do + it 'returns the correct string' do + expect(subject.semanage_options('f')).to eq '-f --' + end + end + %w(d c b s l p).each do |type| + context "with file type #{type}" do + it 'returns the correct string' do + expect(subject.semanage_options(type)).to eq "-f -#{type}" + end + end + end + end + end + + %w( + setsebool_cmd + getsebool_cmd + getenforce_cmd + semanage_cmd + semodule_cmd + ).each do |command| + describe "##{command}" do + it 'returns the correct path' do + expect(subject.send(command)).to eq "/sbin/#{command.gsub(/_cmd/, '')}" + end + + context 'on centos 6' do + before { allow(subject).to receive(:[]).with('platform_version').and_return('6') } + + it 'returns the correct path' do + expect(subject.send(command)).to eq "/usr/sbin/#{command.gsub(/_cmd/, '')}" + end + end + end + end +end diff --git a/spec/unit/recipes/install_spec.rb b/spec/unit/recipes/install_spec.rb new file mode 100644 index 0000000..fee24ef --- /dev/null +++ b/spec/unit/recipes/install_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe 'selinux_policy::install' do + platform 'centos' + + describe 'installs packages' do + it { is_expected.to install_selinux_policy_install('install') } + end +end diff --git a/spec/unit/resources/boolean_spec.rb b/spec/unit/resources/boolean_spec.rb new file mode 100644 index 0000000..a28c589 --- /dev/null +++ b/spec/unit/resources/boolean_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe 'selinux_policy_boolean' do + platform 'centos', '8' + step_into :selinux_policy_boolean + include_context 'selinux enabled' + + recipe do + selinux_policy_boolean 'test' do + value true + end + + selinux_policy_boolean 'nopersist' do + value false + action :set + end + + selinux_policy_boolean 'force' do + value true + force true + end + end + + context 'when not set' do + before do + stub_command("/sbin/getsebool test | grep -q 'on$'").and_return(false) + stub_command("/sbin/getsebool nopersist | grep -q 'off$'").and_return(false) + stub_command("/sbin/getsebool force | grep -q 'on$'").and_return(false) + end + + it do + is_expected.to run_execute('selinux-setbool-test-on').with( + command: '/sbin/setsebool -P test on' + ) + end + + it do + is_expected.to run_execute('selinux-setbool-nopersist-off').with( + command: '/sbin/setsebool nopersist off' + ) + end + + it do + is_expected.to run_execute('selinux-setbool-force-on').with( + command: '/sbin/setsebool -P force on' + ) + end + end + + context 'when set' do + before do + stub_command("/sbin/getsebool test | grep -q 'on$'").and_return(true) + stub_command("/sbin/getsebool nopersist | grep -q 'off$'").and_return(true) + stub_command("/sbin/getsebool force | grep -q 'on$'").and_return(true) + end + + it do + is_expected.to_not run_execute('selinux-setbool-test-on').with( + command: '/sbin/setsebool -P test on' + ) + end + + it do + is_expected.to_not run_execute('selinux-setbool-nopersist-off').with( + command: '/sbin/setsebool nopersist off' + ) + end + + it do + is_expected.to run_execute('selinux-setbool-force-on').with( + command: '/sbin/setsebool -P force on' + ) + end + end +end diff --git a/spec/unit/resources/fcontext_spec.rb b/spec/unit/resources/fcontext_spec.rb new file mode 100644 index 0000000..5df46e4 --- /dev/null +++ b/spec/unit/resources/fcontext_spec.rb @@ -0,0 +1,152 @@ +require 'spec_helper' + +describe 'selinux_policy_fcontext' do + platform 'centos', '8' + step_into :selinux_policy_fcontext + include_context 'selinux enabled' + + recipe do + selinux_policy_fcontext 'addormodify_test' do + file_spec '/file/spec.path' + secontext 'aom_context' + end + + selinux_policy_fcontext 'add_test' do + file_spec '/file/spec.path' + secontext 'add_context' + action :add + end + + selinux_policy_fcontext 'modify_test' do + file_spec '/file/spec.path' + secontext 'modify_context' + action :modify + end + + selinux_policy_fcontext 'delete_test' do + file_spec '/file/spec.path' + secontext 'delete_context' + action :delete + end + + selinux_policy_fcontext 'file_test' do + file_spec '/file/spec.path' + file_type 'f' + secontext 'file_context' + end + + selinux_policy_fcontext 'link_test' do + file_spec '/file/spec.path' + file_type 'l' + secontext 'link_context' + end + end + + before do + stub_command("/sbin/semanage fcontext -l | grep -qP '^/file/spec\\.path\\s+all\\ files\\s+'").and_return(has_context) + %w(aom add modify delete).each do |c| + stub_command("/sbin/semanage fcontext -l | grep -qP '^/file/spec\\.path\\s+all\\ files\\s+system_u:object_r:#{c}_context:s0\\s*$'").and_return(has_correct_context) + end + + stub_command("/sbin/semanage fcontext -l | grep -qP '^/file/spec\\.path\\s+regular\\ file\\s+'").and_return(has_context) + stub_command("/sbin/semanage fcontext -l | grep -qP '^/file/spec\\.path\\s+symbolic\\ link\\s+'").and_return(has_context) + stub_command("/sbin/semanage fcontext -l | grep -qP '^/file/spec\\.path\\s+regular\\ file\\s+system_u:object_r:file_context:s0\\s*$'").and_return(has_correct_context) + stub_command("/sbin/semanage fcontext -l | grep -qP '^/file/spec\\.path\\s+symbolic\\ link\\s+system_u:object_r:link_context:s0\\s*$'").and_return(has_correct_context) + end + + context 'when context is not set' do + let(:has_context) { false } + let(:has_correct_context) { false } + + it do + is_expected.to run_execute('selinux-fcontext-aom_context-add').with( + command: "/sbin/semanage fcontext -a -f a -t aom_context '/file/spec.path'" + ) + end + it { is_expected.to_not run_execute('selinux-fcontext-aom_context-modify') } + + it do + is_expected.to run_execute('selinux-fcontext-add_context-add').with( + command: "/sbin/semanage fcontext -a -f a -t add_context '/file/spec.path'" + ) + end + + it { is_expected.to_not run_execute('selinux-fcontext-modify_context-modify') } + + it { is_expected.to_not run_execute('selinux-fcontext-delete_context-delete') } + + it do + is_expected.to run_execute('selinux-fcontext-file_context-add').with( + command: "/sbin/semanage fcontext -a -f f -t file_context '/file/spec.path'" + ) + end + it { is_expected.to_not run_execute('selinux-fcontext-file_context-modify') } + + it do + is_expected.to run_execute('selinux-fcontext-link_context-add').with( + command: "/sbin/semanage fcontext -a -f l -t link_context '/file/spec.path'" + ) + end + it { is_expected.to_not run_execute('selinux-fcontext-link_context-modify') } + end + + context 'when context is set to other value' do + let(:has_context) { true } + let(:has_correct_context) { false } + + it { is_expected.to_not run_execute('selinux-fcontext-aom_context-add') } + it do + is_expected.to run_execute('selinux-fcontext-aom_context-modify').with( + command: "/sbin/semanage fcontext -m -f a -t aom_context '/file/spec.path'" + ) + end + + it { is_expected.to_not run_execute('selinux-fcontext-add_context-add') } + + it do + is_expected.to run_execute('selinux-fcontext-modify_context-modify').with( + command: "/sbin/semanage fcontext -m -f a -t modify_context '/file/spec.path'" + ) + end + + it { is_expected.to_not run_execute('selinux-fcontext-delete_context-delete') } + + it { is_expected.to_not run_execute('selinux-fcontext-file_context-add') } + it do + is_expected.to run_execute('selinux-fcontext-file_context-modify').with( + command: "/sbin/semanage fcontext -m -f f -t file_context '/file/spec.path'" + ) + end + + it { is_expected.to_not run_execute('selinux-fcontext-link_context-add') } + it do + is_expected.to run_execute('selinux-fcontext-link_context-modify').with( + command: "/sbin/semanage fcontext -m -f l -t link_context '/file/spec.path'" + ) + end + end + + context 'when context is set to correct value' do + let(:has_context) { true } + let(:has_correct_context) { true } + + it { is_expected.to_not run_execute('selinux-fcontext-aom_context-add') } + it { is_expected.to_not run_execute('selinux-fcontext-aom_context-modify') } + + it { is_expected.to_not run_execute('selinux-fcontext-add_context-add') } + + it { is_expected.to_not run_execute('selinux-fcontext-modify_context-modify') } + + it do + is_expected.to run_execute('selinux-fcontext-delete_context-delete').with( + command: "/sbin/semanage fcontext -f a -d '/file/spec.path'" + ) + end + + it { is_expected.to_not run_execute('selinux-fcontext-file_context-add') } + it { is_expected.to_not run_execute('selinux-fcontext-file_context-modify') } + + it { is_expected.to_not run_execute('selinux-fcontext-link_context-add') } + it { is_expected.to_not run_execute('selinux-fcontext-link_context-modify') } + end +end diff --git a/spec/unit/resources/install_spec.rb b/spec/unit/resources/install_spec.rb new file mode 100644 index 0000000..e0dd69d --- /dev/null +++ b/spec/unit/resources/install_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'selinux_policy_install' do + step_into :selinux_policy_install + + recipe do + selinux_policy_install 'test' + end + + context 'on centos 6' do + platform 'centos', '6' + + it do + is_expected.to install_package(%w(policycoreutils-python selinux-policy setools-console make)) + end + end + + context 'on centos 7' do + platform 'centos', '7' + + it do + is_expected.to install_package(%w(policycoreutils-python selinux-policy-devel setools-console make)) + end + end + + context 'on centos 8' do + platform 'centos', '8' + + it do + is_expected.to install_package(%w(policycoreutils-python-utils selinux-policy-devel setools-console make)) + end + end + + context 'on fedora' do + platform 'fedora' + + it do + is_expected.to install_package(%w(policycoreutils selinux-policy-devel setools-console make)) + 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..3bfc6a6 --- /dev/null +++ b/spec/unit/resources/module_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper' + +describe 'selinux_policy_module' do + platform 'centos', '8' + step_into :selinux_policy_module + include_context 'selinux enabled' + + before do + allow(Chef::Config).to receive(:[]).and_call_original + allow(Chef::Config).to receive(:[]).with(:file_cache_path).and_return('/cachedir') + end + + recipe do + selinux_policy_module 'test' do + content 'foo' + end + + selinux_policy_module 'directory' do + directory_source 'foo/bar' + cookbook 'baz' + end + end + + context 'when module is not installed' do + before do + stub_command('/usr/bin/make -f /usr/share/selinux/devel/Makefile test.pp -q').and_return(false) + stub_command("false || ! (/sbin/semodule -l | grep -w '^test') ").and_return(true) + stub_command('/usr/bin/make -f /usr/share/selinux/devel/Makefile directory.pp -q').and_return(false) + stub_command("false || ! (/sbin/semodule -l | grep -w '^directory') ").and_return(true) + end + + it { is_expected.to create_directory('/cachedir/test') } + it do + is_expected.to create_file('/cachedir/test/test.te').with( + content: 'foo' + ) + end + it do + is_expected.to run_execute('semodule-compile-test').with( + command: '/usr/bin/make -f /usr/share/selinux/devel/Makefile test.pp' + ) + end + it do + is_expected.to run_execute('semodule-install-test').with( + command: '/sbin/semodule -i /cachedir/test/test.pp' + ) + end + + it { is_expected.to create_directory('/cachedir/directory') } + it do + is_expected.to create_remote_directory('/cachedir/directory').with( + source: 'foo/bar', + cookbook: 'baz' + ) + end + it do + is_expected.to run_execute('semodule-compile-directory').with( + command: '/usr/bin/make -f /usr/share/selinux/devel/Makefile directory.pp' + ) + end + it do + is_expected.to run_execute('semodule-install-directory').with( + command: '/sbin/semodule -i /cachedir/directory/directory.pp' + ) + end + end + + context 'when module is installed' do + before do + stub_command('/usr/bin/make -f /usr/share/selinux/devel/Makefile test.pp -q').and_return(true) + stub_command("false || ! (/sbin/semodule -l | grep -w '^test') ").and_return(false) + stub_command('/usr/bin/make -f /usr/share/selinux/devel/Makefile directory.pp -q').and_return(true) + stub_command("false || ! (/sbin/semodule -l | grep -w '^directory') ").and_return(false) + end + + it { is_expected.to_not run_execute('semodule-compile-test') } + it { is_expected.to_not run_execute('semodule-install-test') } + + it { is_expected.to_not run_execute('semodule-compile-directory') } + it { is_expected.to_not run_execute('semodule-install-directory') } + end + + context 'when both content and source_dir are specified' do + recipe do + selinux_policy_module 'both' do + content 'foo' + directory_source 'bar' + end + end + + it do + expect { chef_run }.to raise_error(RuntimeError) + end + end +end diff --git a/spec/unit/resources/permissive_spec.rb b/spec/unit/resources/permissive_spec.rb new file mode 100644 index 0000000..04e4fa1 --- /dev/null +++ b/spec/unit/resources/permissive_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe 'selinux_policy_permissive' do + platform 'centos', '8' + step_into :selinux_policy_permissive + include_context 'selinux enabled' + + recipe do + selinux_policy_permissive 'test' + + selinux_policy_permissive 'delete' do + context 'delete_context' + action :delete + end + end + + context 'when permissive is not set' do + before do + stub_command("/sbin/semanage permissive -l | grep -Fxq 'test'").and_return(false) + stub_command("/sbin/semanage permissive -l | grep -Fxq 'delete_context'").and_return(false) + end + + it do + is_expected.to run_execute('selinux-permissive-test-add').with( + command: "/sbin/semanage permissive -a 'test'" + ) + end + + it { is_expected.to_not run_execute('selinux-permissive-delete_context-delete') } + end + + context 'when permissive is set' do + before do + stub_command("/sbin/semanage permissive -l | grep -Fxq 'test'").and_return(true) + stub_command("/sbin/semanage permissive -l | grep -Fxq 'delete_context'").and_return(true) + end + + it { is_expected.to_not run_execute('selinux-permissive-test-add') } + + it do + is_expected.to run_execute('selinux-permissive-delete_context-delete').with( + command: "/sbin/semanage permissive -d 'delete_context'" + ) + end + end +end diff --git a/spec/unit/resources/port_spec.rb b/spec/unit/resources/port_spec.rb new file mode 100644 index 0000000..e644ec0 --- /dev/null +++ b/spec/unit/resources/port_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe 'selinux_policy_port' do + platform 'centos', '8' + step_into :selinux_policy_port + include_context 'selinux enabled' + + recipe do + selinux_policy_port '12345' do + protocol 'tcp' + secontext 'aom_context' + end + + selinux_policy_port '12346' do + protocol 'tcp' + secontext 'add_context' + action :add + end + + selinux_policy_port '12347' do + protocol 'tcp' + secontext 'modify_context' + action :modify + end + + selinux_policy_port '12348' do + protocol 'tcp' + action :delete + end + end + + before do + [ + %w(aom 12345), + %w(add 12346), + %w(modify 12347), + %w(delete 12348), + ].each do |c, p| + stub_command("seinfo --portcon=#{p} | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -q ^").and_return(has_context) + stub_command("seinfo --portcon=#{p} | grep 'portcon tcp' | awk -F: '$(NF-1) !~ /reserved_port_t$/ && $(NF-3) !~ /[0-9]*-[0-9]*/ {print $(NF-1)}' | grep -P '#{c}_context'").and_return(has_correct_context) + end + end + + context 'when port is not defined' do + let(:has_context) { false } + let(:has_correct_context) { false } + + it do + is_expected.to run_execute('selinux-port-12345-add').with( + command: '/sbin/semanage port -a -t aom_context -p tcp 12345' + ) + end + it { is_expected.to_not run_execute('selinux-port-12345-modify') } + + it do + is_expected.to run_execute('selinux-port-12346-add').with( + command: '/sbin/semanage port -a -t add_context -p tcp 12346' + ) + end + + it { is_expected.to_not run_execute('selinux-port-12347-modify') } + + it { is_expected.to_not run_execute('selinux-port-12348-delete') } + end + + context 'when port has other value' do + let(:has_context) { true } + let(:has_correct_context) { false } + + it { is_expected.to_not run_execute('selinux-port-12345-add') } + it do + is_expected.to run_execute('selinux-port-12345-modify').with( + command: '/sbin/semanage port -m -t aom_context -p tcp 12345' + ) + end + + it { is_expected.to_not run_execute('selinux-port-12346-add') } + + it do + is_expected.to run_execute('selinux-port-12347-modify').with( + command: '/sbin/semanage port -m -t modify_context -p tcp 12347' + ) + end + + it do + is_expected.to run_execute('selinux-port-12348-delete').with( + command: '/sbin/semanage port -d -p tcp 12348' + ) + end + end + + context 'when port has correct value' do + let(:has_context) { true } + let(:has_correct_context) { true } + + it { is_expected.to_not run_execute('selinux-port-12345-add') } + it { is_expected.to_not run_execute('selinux-port-12345-modify') } + + it { is_expected.to_not run_execute('selinux-port-12346-add') } + + it { is_expected.to_not run_execute('selinux-port-12347-modify') } + + it do + is_expected.to run_execute('selinux-port-12348-delete').with( + command: '/sbin/semanage port -d -p tcp 12348' + ) + end + end + + context 'when given a bad port' do + let(:has_context) { false } + let(:has_correct_context) { false } + + recipe do + selinux_policy_port 'bad port' do + port 'something invalid' + protocol 'tcp' + secontext 'foo' + end + end + + it do + expect { chef_run }.to raise_error(ArgumentError) + end + end +end diff --git a/test/cookbooks/test/.gitignore b/test/cookbooks/test/.gitignore deleted file mode 100644 index ec2a890..0000000 --- a/test/cookbooks/test/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -.vagrant -Berksfile.lock -*~ -*# -.#* -\#*# -.*.sw[a-z] -*.un~ - -# Bundler -Gemfile.lock -bin/* -.bundle/* - -.kitchen/ -.kitchen.local.yml diff --git a/test/cookbooks/test/files/default/dirtest_module/dirtest.fc b/test/cookbooks/test/files/default/dirtest_module/dirtest.fc new file mode 100644 index 0000000..02533cd --- /dev/null +++ b/test/cookbooks/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/test/files/default/dirtest_module/dirtest.if b/test/cookbooks/test/files/default/dirtest_module/dirtest.if new file mode 100644 index 0000000..146b760 --- /dev/null +++ b/test/cookbooks/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/test/files/default/dirtest_module/dirtest.te b/test/cookbooks/test/files/default/dirtest_module/dirtest.te new file mode 100644 index 0000000..0e6d323 --- /dev/null +++ b/test/cookbooks/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/test/files/default/testo/testo.te b/test/cookbooks/test/files/default/testo/testo.te deleted file mode 100644 index ed4aedb..0000000 --- a/test/cookbooks/test/files/default/testo/testo.te +++ /dev/null @@ -1,10 +0,0 @@ -module testo 1.0; - -require { - type httpd_t; - type var_t; - class file { read getattr open }; -} - -#============= httpd_t ============== -allow httpd_t var_t:file { read getattr open }; diff --git a/test/cookbooks/test/metadata.rb b/test/cookbooks/test/metadata.rb index e00e4ff..eb3758f 100644 --- a/test/cookbooks/test/metadata.rb +++ b/test/cookbooks/test/metadata.rb @@ -2,7 +2,7 @@ maintainer 'Sous Chefs' maintainer_email 'test' license 'Apache-2.0' -description 'Wrapper cookbook to test selinux_policy LWRPs' +description 'Wrapper cookbook to test selinux_policy' version '0.1.0' depends 'selinux_policy' diff --git a/test/cookbooks/test/recipes/bad_port.rb b/test/cookbooks/test/recipes/bad_port.rb deleted file mode 100644 index c60eff0..0000000 --- a/test/cookbooks/test/recipes/bad_port.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Expect to fail with a bad port string - -bad_port = selinux_policy_port 'bad_port' do - action :nothing - port '1a2b3c' - protocol 'tcp' - secontext 'http_port_t' -end - -ruby_block 'bad-port' do - block do - caught = false - begin - bad_port.provider_for_action(:addormodify).run_action - rescue ArgumentError - # We're happy. - caught = true - end - raise 'Invalid port was not complained about' unless caught - end -end diff --git a/test/cookbooks/test/recipes/boolean.rb b/test/cookbooks/test/recipes/boolean.rb new file mode 100644 index 0000000..32fb1ba --- /dev/null +++ b/test/cookbooks/test/recipes/boolean.rb @@ -0,0 +1,7 @@ +selinux_policy_boolean 'httpd_can_network_connect' do + value true +end + +selinux_policy_boolean 'httpd_enable_cgi' do + value false +end diff --git a/test/cookbooks/test/recipes/fcontext.rb b/test/cookbooks/test/recipes/fcontext.rb index c28d35e..96b253d 100644 --- a/test/cookbooks/test/recipes/fcontext.rb +++ b/test/cookbooks/test/recipes/fcontext.rb @@ -1,71 +1,26 @@ -dir_name = '/var/www/tester' -subdir = '/var/www/tester/testregex' -regex = '/var/www/tester(/.*)?' -context = 'httpd_tmp_t' -context2 = 'boot_t' +directory '/opt/selinux-test' -selinux_policy_fcontext dir_name do - secontext context +%w( foo bar baz ).each do |f| + file "/opt/selinux-test/#{f}" end -directory dir_name do - recursive true +link '/opt/selinux-test/quux' do + to '/opt/selinux-test/foo' + link_type :symbolic end -# TODO: Inspec test -# # Fail if dir isn't actually set -# execute 'true dir context' do -# not_if "stat -c %C #{dir_name} | grep #{context}" -# end - -# Should not run again -selinux_policy_fcontext 'nomod on dir_name' do - file_spec dir_name - action :modify - secontext context -end - -selinux_policy_fcontext 'modme' do - file_spec dir_name - action :modify - secontext context2 -end - -# # TODO Inspec test -# # Fail if dir hasn't modified context -# execute 'true dir context2' do -# not_if "stat -c %C #{dir_name} | grep #{context2}" -# end - -selinux_policy_fcontext 'deleteme dir_name' do - file_spec dir_name - action :delete +# single file +selinux_policy_fcontext '/opt/selinux-test/foo' do + secontext 'httpd_sys_content_t' end -# Testing regexes -directory subdir do - recursive true +# regex +selinux_policy_fcontext '/opt/selinux-test/b.+' do + secontext 'boot_t' end -selinux_policy_fcontext regex do - secontext context -end - -# TODO: Inspec test -# # Fail if subdir hasn't modified context -# execute 'true subdir context' do -# not_if "stat -c %C #{subdir} | grep #{context}" -# end - -selinux_policy_fcontext 'deleteregex' do - file_spec regex - action :delete -end - -execute "restorecon -iR #{dir_name}" # Restore original context - -# Shouldn't be in any of our contexts anymore -execute 'true dir context and context2' do - not_if "stat -c %C #{dir_name} | grep -v #{context}" - not_if "stat -c %C #{dir_name} | grep -v #{context2}" +# file type +selinux_policy_fcontext '/opt/selinux-test/.+' do + secontext 'httpd_tmp_t' + file_type 'l' end diff --git a/test/cookbooks/test/recipes/fcontext_filetype.rb b/test/cookbooks/test/recipes/fcontext_filetype.rb deleted file mode 100644 index 03d4c5d..0000000 --- a/test/cookbooks/test/recipes/fcontext_filetype.rb +++ /dev/null @@ -1,106 +0,0 @@ -# Define a single fcontext -ruby_block 'fail-mismatch-fcontext-filetype' do - action :nothing - block do - raise 'Fail block was invoked' - end -end - -basedir = '/var/www' -symlink = '/var/www/tester_symlink' -context_dir = 'httpd_sys_content_t' -context_dir2 = 'httpd_tmp_t' -context_symlink = 'httpd_sys_rw_content_t' - -selinux_policy_fcontext basedir do - secontext context_dir - file_type 'd' -end - -directory basedir do - recursive true -end - -link symlink do - to basedir -end - -# Should adapt the symlink itself -selinux_policy_fcontext symlink do - secontext context_symlink - file_type 'l' -end - -# TODO: Move to inspec -# Fail if dir isn't actually set -# execute 'true basedir context_dir' do -# not_if "stat -c %C #{basedir} | grep #{context_dir}" -# notifies :run, 'ruby_block[fail-mismatch-fcontext-filetype]', :immediate -# end - -# Should not run again -selinux_policy_fcontext 'nomod on basedir with context_dir' do - file_spec basedir - action :modify - secontext context_dir - file_type 'd' - # notifies :run, 'ruby_block[fail-mismatch-fcontext-filetype]', :immediate -end - -selinux_policy_fcontext 'modme on basedir with context_dir2' do - file_spec basedir - action :modify - secontext context_dir2 - file_type 'd' -end - -# Fail if dir hasn't modified context -execute 'true basedir' do - not_if "stat -c %C #{basedir} | grep #{context_dir2}" - # notifies :run, 'ruby_block[fail-mismatch-fcontext-filetype]', :immediate -end - -# Fail if symlink has changed context -execute 'true symlink' do - not_if "stat -c %C #{symlink} | grep #{context_symlink}" - # notifies :run, 'ruby_block[fail-mismatch-fcontext-filetype]', :immediate -end - -# Should not run again -selinux_policy_fcontext 'symlink-nomod' do - file_spec symlink - action :modify - secontext context_symlink - file_type 'l' - # notifies :run, 'ruby_block[fail-mismatch-fcontext-filetype]', :immediate -end - -# Should not change the symlink itself, so shouldn't change -selinux_policy_fcontext 'symlink-nomod2' do - file_spec symlink - action :modify - secontext context_dir2 - # notifies :run, 'ruby_block[fail-mismatch-fcontext-filetype]', :immediate -end - -selinux_policy_fcontext 'deleteme basedir' do - file_spec basedir - file_type 'd' - action :delete -end - -selinux_policy_fcontext 'deleteme_symlink' do - file_spec symlink - file_type 'l' - action :delete -end - -execute "restorecon #{basedir}" # Restore original context - -# Shouldn't be in any of our contexts anymore -execute 'true basedir and symlink' do - not_if "stat -c %C #{basedir} | grep -v #{context_dir}" - not_if "stat -c %C #{basedir} | grep -v #{context_dir2}" - not_if "stat -c %C #{symlink} | grep -v #{context_symlink}" - # notifies :run, 'ruby_block[fail-mismatch-fcontext-filetype]', :immediate -end diff --git a/test/cookbooks/test/recipes/setup.rb b/test/cookbooks/test/recipes/install.rb similarity index 76% rename from test/cookbooks/test/recipes/setup.rb rename to test/cookbooks/test/recipes/install.rb index 9316296..1bf3ff2 100644 --- a/test/cookbooks/test/recipes/setup.rb +++ b/test/cookbooks/test/recipes/install.rb @@ -5,5 +5,5 @@ reboot 'SELinux Reboot' do action :nothing reason 'Rebooting to enable SELinux.' - delay_mins 7 + not_if "sestatus | grep -qEi 'SELinux status:\s+enabled'" end diff --git a/test/cookbooks/test/recipes/module.rb b/test/cookbooks/test/recipes/module.rb index 5f6b810..230f68a 100644 --- a/test/cookbooks/test/recipes/module.rb +++ b/test/cookbooks/test/recipes/module.rb @@ -1,21 +1,6 @@ -# Plays around with an SEModule - -module_content = <<-eos - policy_module(testy, 1.0.0) - type testy_t; -eos - -selinux_policy_module 'testy' do - content module_content -end - -# Should not be reinstalled -selinux_policy_module 'testy-again' do - module_name 'testy' - content module_content -end - -selinux_policy_module 'testy-bye' do - action :remove - module_name 'testy' +selinux_policy_module 'test_module' do + content <<~EOM + policy_module(test_module, 0.0.1) + type test_module_t; + EOM end diff --git a/test/cookbooks/test/recipes/module_directory.rb b/test/cookbooks/test/recipes/module_directory.rb index 315fd02..23a4364 100644 --- a/test/cookbooks/test/recipes/module_directory.rb +++ b/test/cookbooks/test/recipes/module_directory.rb @@ -1,5 +1,5 @@ -# Creates a module based on a source directory +file '/usr/bin/dirtest' -selinux_policy_module 'testo' do - directory_source 'testo' +selinux_policy_module 'dirtest' do + directory_source 'dirtest_module' end diff --git a/test/cookbooks/test/recipes/module_remove.rb b/test/cookbooks/test/recipes/module_remove.rb new file mode 100644 index 0000000..8f90ac8 --- /dev/null +++ b/test/cookbooks/test/recipes/module_remove.rb @@ -0,0 +1,10 @@ +selinux_policy_module 'test_module' do + content <<~EOM + policy_module(test_module, 0.0.1) + type test_module_t; + EOM +end + +selinux_policy_module 'test_module' do + action :remove +end diff --git a/test/cookbooks/test/recipes/permissive.rb b/test/cookbooks/test/recipes/permissive.rb new file mode 100644 index 0000000..ce07732 --- /dev/null +++ b/test/cookbooks/test/recipes/permissive.rb @@ -0,0 +1,5 @@ +selinux_policy_permissive 'httpd_t' + +selinux_policy_permissive 'user_t' do + action :delete +end diff --git a/test/cookbooks/test/recipes/port.rb b/test/cookbooks/test/recipes/port.rb new file mode 100644 index 0000000..2bae85a --- /dev/null +++ b/test/cookbooks/test/recipes/port.rb @@ -0,0 +1,11 @@ +%w(tcp udp).each do |prot| + selinux_policy_port '29000' do + protocol prot + secontext 'http_port_t' + end +end + +selinux_policy_port '29001' do + protocol 'tcp' + secontext 'ssh_port_t' +end diff --git a/test/cookbooks/test/recipes/range_port.rb b/test/cookbooks/test/recipes/range_port.rb deleted file mode 100644 index 86f07b8..0000000 --- a/test/cookbooks/test/recipes/range_port.rb +++ /dev/null @@ -1,6 +0,0 @@ -# Ensures a port is correctly detected when a range is already defined - -# Manually define ports -execute 'semanage port -a -t http_port_t -p tcp 1900-1902' do - not_if 'semanage port -l | grep http_port_t | grep 1900-1902' -end diff --git a/test/cookbooks/test/recipes/single_default_port.rb b/test/cookbooks/test/recipes/single_default_port.rb deleted file mode 100644 index a014081..0000000 --- a/test/cookbooks/test/recipes/single_default_port.rb +++ /dev/null @@ -1,12 +0,0 @@ -# Define a single default port - -include_recipe 'selinux_policy::install' - -execute 'setenforce 1' do - not_if '[ "$(getenforce)" = "Enforcing" ]' -end - -selinux_policy_port '10080' do - protocol 'tcp' - secontext 'http_port_t' -end diff --git a/test/cookbooks/test/recipes/single_port.rb b/test/cookbooks/test/recipes/single_port.rb deleted file mode 100644 index ba7658f..0000000 --- a/test/cookbooks/test/recipes/single_port.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Define a single port -selinux_policy_port '1080' do - protocol 'tcp' - secontext 'http_port_t' -end diff --git a/test/cookbooks/test/recipes/single_port_add_to_range.rb b/test/cookbooks/test/recipes/single_port_add_to_range.rb deleted file mode 100644 index c8f97d5..0000000 --- a/test/cookbooks/test/recipes/single_port_add_to_range.rb +++ /dev/null @@ -1,12 +0,0 @@ -# Define a single port - -include_recipe 'selinux_policy::install' - -execute 'setenforce 1' do - not_if '[ "$(getenforce)" = "Enforcing" ]' -end - -selinux_policy_port '10080' do - protocol 'tcp' - secontext 'http_port_t' -end diff --git a/test/cookbooks/test/recipes/twice_port.rb b/test/cookbooks/test/recipes/twice_port.rb deleted file mode 100644 index c4dc6ac..0000000 --- a/test/cookbooks/test/recipes/twice_port.rb +++ /dev/null @@ -1,23 +0,0 @@ -# Define the same port twice and make sure nothing is done the second time - -# Failure generator -ruby_block 'fail-twice' do - action :nothing - block do - raise 'Fail block was invoked' - end -end - -# Define a single port -selinux_policy_port '1081' do - protocol 'tcp' - secontext 'http_port_t' -end - -# Again -selinux_policy_port '1081-again' do - port 1081 - protocol 'tcp' - secontext 'http_port_t' - notifies :run, 'ruby_block[fail-twice]', :immediately -end diff --git a/test/integration/boolean/inspec/boolean_spec.rb b/test/integration/boolean/inspec/boolean_spec.rb new file mode 100644 index 0000000..f9a4c20 --- /dev/null +++ b/test/integration/boolean/inspec/boolean_spec.rb @@ -0,0 +1,7 @@ +describe command('getsebool httpd_can_network_connect') do + its('stdout') { should match 'httpd_can_network_connect --> on' } +end + +describe command('getsebool httpd_enable_cgi') do + its('stdout') { should match 'httpd_enable_cgi --> off' } +end diff --git a/test/integration/fcontext/inspec/fcontext_spec.rb b/test/integration/fcontext/inspec/fcontext_spec.rb new file mode 100644 index 0000000..083e093 --- /dev/null +++ b/test/integration/fcontext/inspec/fcontext_spec.rb @@ -0,0 +1,7 @@ +describe command('stat -c "%n %C" /opt/selinux-test/*') do + its('stdout') { should match 'foo unconfined_u:object_r:httpd_sys_content_t:s0' } + its('stdout') { should match 'bar unconfined_u:object_r:boot_t:s0' } + its('stdout') { should match 'baz unconfined_u:object_r:boot_t:s0' } + its('stdout') { should match 'quux unconfined_u:object_r:httpd_tmp_t:s0' } + its('stdout') { should_not match 'usr_t:s0' } +end diff --git a/test/integration/install/inspec/install_spec.rb b/test/integration/install/inspec/install_spec.rb new file mode 100644 index 0000000..8feb12d --- /dev/null +++ b/test/integration/install/inspec/install_spec.rb @@ -0,0 +1,8 @@ +describe command('sestatus') do + its('stdout') { should cmp /SELinux status:\s+enabled/ } +end + +describe command('getenforce') do + its('stdout') { should cmp /Enforcing|Permissive/ } + its('stdout') { should_not cmp /Disabled/ } +end diff --git a/test/integration/module-directory/inspec/module_directory_spec.rb b/test/integration/module-directory/inspec/module_directory_spec.rb new file mode 100644 index 0000000..7752b2b --- /dev/null +++ b/test/integration/module-directory/inspec/module_directory_spec.rb @@ -0,0 +1,18 @@ +describe command('semodule -l') do + its('stdout') { should match 'dirtest' } +end + +%w( + dirtest_t + dirtest_exec_t + dirtest_log_t + dirtest_tmp_t +).each do |type| + describe command("seinfo --type=#{type}") do + its('stdout') { should match type } + end +end + +describe command('semanage fcontext -l') do + its('stdout') { should match %r{/usr/bin/dirtest\s+regular file\s+system_u:object_r:dirtest_exec_t:s0} } +end diff --git a/test/integration/module-remove/inspec/module_remove_spec.rb b/test/integration/module-remove/inspec/module_remove_spec.rb new file mode 100644 index 0000000..fb45608 --- /dev/null +++ b/test/integration/module-remove/inspec/module_remove_spec.rb @@ -0,0 +1,7 @@ +describe command('semodule -l') do + its('stdout') { should_not match 'test_module' } +end + +describe command('seinfo --type=test_module_t') do + its('stdout') { should_not match 'test_module_t' } +end diff --git a/test/integration/module/inspec/module_spec.rb b/test/integration/module/inspec/module_spec.rb new file mode 100644 index 0000000..f04595d --- /dev/null +++ b/test/integration/module/inspec/module_spec.rb @@ -0,0 +1,7 @@ +describe command('semodule -l') do + its('stdout') { should match 'test_module' } +end + +describe command('seinfo --type=test_module_t') do + its('stdout') { should match 'test_module_t' } +end diff --git a/test/integration/permissive/inspec/permissive_spec.rb b/test/integration/permissive/inspec/permissive_spec.rb new file mode 100644 index 0000000..4631331 --- /dev/null +++ b/test/integration/permissive/inspec/permissive_spec.rb @@ -0,0 +1,4 @@ +describe command('semanage permissive -l') do + its('stdout') { should match 'httpd_t' } + its('stdout') { should_not match 'user_t' } +end diff --git a/test/integration/port/inspec/port_spec.rb b/test/integration/port/inspec/port_spec.rb new file mode 100644 index 0000000..18bcc1d --- /dev/null +++ b/test/integration/port/inspec/port_spec.rb @@ -0,0 +1,8 @@ +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