# HG changeset patch
# User Robert Marshall <rmarshall@gitlab.com>
# Date 1707978618 0
#      Thu Feb 15 06:30:18 2024 +0000
# Node ID 82723b10e357ef7da0beb7e37bb462af81785865
# Parent  4f7123044f29341b913d7d6c93ae4ab42026121f
Consolidate SELinux policy into one module

- Create a single versioned SELinux policy module for GitLab similar to
  other policy module components.
- The first iteration of the consolidated policy is opt-in if the user
  sets `package['selinux_policy_version'] to anything other than nil.

Closes https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/7453

Closes https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/8235

Changelog: changed
Signed-off-by: Robert Marshall <rmarshall@gitlab.com>

diff --git a/files/gitlab-config-template/gitlab.rb.template b/files/gitlab-config-template/gitlab.rb.template
--- a/files/gitlab-config-template/gitlab.rb.template
+++ b/files/gitlab-config-template/gitlab.rb.template
@@ -2799,6 +2799,11 @@
 # package['generate_default_secrets'] = true
 ##! Set to false to prevent creating the default `gitlab-secrets.json` file
 # package['generate_secrets_json_file'] = true
+
+##! Settings to control SELinux policy
+##! Experimental. Set to 1.0 to switch from legacy multiple policy modules to
+##! newer single `gitlab` SELinux policy module.
+# package['selinux_policy_version'] = nil
 ################################################################################
 ################################################################################
 ##                  Configuration Settings for GitLab EE only                 ##
diff --git a/files/gitlab-cookbooks/gitlab/recipes/selinux.rb b/files/gitlab-cookbooks/gitlab/recipes/selinux.rb
--- a/files/gitlab-cookbooks/gitlab/recipes/selinux.rb
+++ b/files/gitlab-cookbooks/gitlab/recipes/selinux.rb
@@ -17,21 +17,50 @@
 
 if SELinuxDistroHelper.selinux_supported?
   ssh_keygen_module = 'gitlab-7.2.0-ssh-keygen'
-  execute "semodule -i /opt/gitlab/embedded/selinux/#{ssh_keygen_module}.pp" do
-    not_if "getenforce | grep Disabled"
-    not_if "semodule -l | grep '^#{ssh_keygen_module}\\s'"
-  end
+  authorized_keys_module = 'gitlab-10.5.0-ssh-authorized-keys'
+  gitlab_shell_module = 'gitlab-13.5.0-gitlab-shell'
+  gitlab_unified_module = 'gitlab'
+
+  if SELinuxHelper.use_unified_policy?(node)
+    execute "semodule -i /opt/gitlab/embedded/selinux/#{gitlab_unified_module}.pp" do
+      not_if "getenforce | grep Disabled"
+      not_if "semodule -l | grep -E '^#{gitlab_unified_module}([[:space:]]|$)'"
+    end
+
+    execute "semodule -r #{ssh_keygen_module}" do
+      not_if "getenforce | grep Disabled"
+      only_if "semodule -l | grep -E '^#{ssh_keygen_module}([[:space:]]|$)'"
+    end
+
+    execute "semodule -r #{authorized_keys_module}" do
+      not_if "getenforce | grep Disabled"
+      only_if "semodule -l | grep -E '^#{authorized_keys_module}([[:space:]]|$)'"
+    end
 
-  authorized_keys_module = 'gitlab-10.5.0-ssh-authorized-keys'
-  execute "semodule -i /opt/gitlab/embedded/selinux/#{authorized_keys_module}.pp" do
-    not_if "getenforce | grep Disabled"
-    not_if "semodule -l | grep '^#{authorized_keys_module}\\s'"
-  end
+    execute "semodule -r #{gitlab_shell_module}" do
+      not_if "getenforce | grep Disabled"
+      only_if "semodule -l | grep -E '^#{gitlab_shell_module}([[:space:]]|$)'"
+    end
+  else
+    execute "semodule -i /opt/gitlab/embedded/selinux/#{ssh_keygen_module}.pp" do
+      not_if "getenforce | grep Disabled"
+      not_if "semodule -l | grep -E '^#{ssh_keygen_module}([[:space:]]|$)'"
+    end
 
-  gitlab_shell_module = 'gitlab-13.5.0-gitlab-shell'
-  execute "semodule -i /opt/gitlab/embedded/selinux/#{gitlab_shell_module}.pp" do
-    not_if "getenforce | grep Disabled"
-    not_if "semodule -l | grep '^#{gitlab_shell_module}\\s'"
+    execute "semodule -i /opt/gitlab/embedded/selinux/#{authorized_keys_module}.pp" do
+      not_if "getenforce | grep Disabled"
+      not_if "semodule -l | grep -E '^#{authorized_keys_module}([[:space:]]|$)'"
+    end
+
+    execute "semodule -i /opt/gitlab/embedded/selinux/#{gitlab_shell_module}.pp" do
+      not_if "getenforce | grep Disabled"
+      not_if "semodule -l | grep -E '^#{gitlab_shell_module}([[:space:]]|$)'"
+    end
+
+    execute "semodule -r #{gitlab_unified_module}" do
+      not_if "getenforce | grep Disabled"
+      only_if "semodule -l | grep -E '^#{gitlab_unified_module}([[:space:]]|$)'"
+    end
   end
 end
 
diff --git a/files/gitlab-cookbooks/package/attributes/default.rb b/files/gitlab-cookbooks/package/attributes/default.rb
--- a/files/gitlab-cookbooks/package/attributes/default.rb
+++ b/files/gitlab-cookbooks/package/attributes/default.rb
@@ -40,3 +40,6 @@
 default['runit']['service_dir'] = '/opt/gitlab/service'
 default['runit']['sv_dir'] = '/opt/gitlab/sv'
 default['runit']['lsb_init_dir'] = '/opt/gitlab/init'
+
+# SELinux Policy options to assist with transition to unified policy
+default['package']['selinux_policy_version'] = nil
diff --git a/files/gitlab-cookbooks/package/libraries/helpers/selinux_helper.rb b/files/gitlab-cookbooks/package/libraries/helpers/selinux_helper.rb
--- a/files/gitlab-cookbooks/package/libraries/helpers/selinux_helper.rb
+++ b/files/gitlab-cookbooks/package/libraries/helpers/selinux_helper.rb
@@ -4,6 +4,12 @@
   class << self
     include ShellOutHelper
 
+    def use_unified_policy?(node)
+      return false if node['package']['selinux_policy_version'].nil?
+
+      true
+    end
+
     def commands(node)
       ssh_dir = File.join(node['gitlab']['user']['home'], ".ssh")
       authorized_keys = node['gitlab']['gitlab_shell']['auth_file']
diff --git a/files/gitlab-selinux/gitlab.te b/files/gitlab-selinux/gitlab.te
new file mode 100644
--- /dev/null
+++ b/files/gitlab-selinux/gitlab.te
@@ -0,0 +1,39 @@
+module gitlab 1.0.0;
+
+############################
+# External policy components
+############################
+require {
+    type http_cache_port_t;
+    type httpd_t;
+    type init_tmp_t;
+    type ssh_keygen_t;
+    type sshd_t;
+    type sshd_t;
+    type var_log_t;
+
+    attribute file_type;
+
+    class file { open read getattr };
+    class sock_file { write read };
+    class tcp_socket name_connect;
+}
+
+################################
+# GitLab policy type definitions
+################################
+type gitlab_shell_t;
+
+typeattribute gitlab_shell_t file_type;
+
+#####################
+# Access Vector Rules
+#####################
+allow ssh_keygen_t init_tmp_t:file open;
+
+allow sshd_t http_cache_port_t:tcp_socket name_connect;
+allow sshd_t var_log_t:file open;
+allow sshd_t gitlab_shell_t:file { read open getattr };
+allow sshd_t gitlab_shell_t:sock_file write;
+
+allow httpd_t gitlab_shell_t:sock_file { read write };
diff --git a/spec/chef_helper.rb b/spec/chef_helper.rb
--- a/spec/chef_helper.rb
+++ b/spec/chef_helper.rb
@@ -50,11 +50,19 @@
   end
 
   config.before(:each, type: :chef) do
+    ssh_keygen_module = 'gitlab-7.2.0-ssh-keygen'
+    authorized_keys_module = 'gitlab-10.5.0-ssh-authorized-keys'
+    gitlab_shell_module = 'gitlab-13.5.0-gitlab-shell'
+    gitlab_unified_module = 'gitlab'
+
     stub_command('id -Z').and_return(false)
     stub_command("grep 'CS:123456:respawn:/opt/gitlab/embedded/bin/runsvdir-start' /etc/inittab").and_return('')
     stub_command(%r{\(test -f /var/opt/gitlab/gitlab-rails/upgrade-status/db-migrate-\h+-\) && \(cat /var/opt/gitlab/gitlab-rails/upgrade-status/db-migrate-\h+- | grep -Fx 0\)}).and_return(false)
     stub_command("getenforce | grep Disabled").and_return(true)
-    stub_command("semodule -l | grep '^#gitlab-7.2.0-ssh-keygen\\s'").and_return(true)
+    stub_command("semodule -l | grep '^#{ssh_keygen_module}([[:space:]]|$)'").and_return(true)
+    stub_command("semodule -l | grep '^#{authorized_keys_module}([[:space:]]|$)'").and_return(true)
+    stub_command("semodule -l | grep '^#{gitlab_shell_module}([[:space:]]|$)'").and_return(true)
+    stub_command("semodule -l | grep -E '^#{gitlab_unified_module}([[:space:]]|$)'").and_return(true)
     stub_command(%r{set \-x \&\& \[ \-d "[^"]\" \]}).and_return(false)
     stub_command(%r{set \-x \&\& \[ "\$\(stat \-\-printf='[^']*' \$\(readlink -f /[^\)]*\)\) }).and_return(false)
     stub_command('/opt/gitlab/embedded/bin/psql --version').and_return("fake_version")