Commit 2d899432 authored by DJ Mountney's avatar DJ Mountney
Browse files

Merge branch 'john_long-add-selinux-module-for-gitlab-shell' into 'master'

Add selinux module for gitlab-shell

Closes #5694

See merge request gitlab-org/omnibus-gitlab!4598
parents a48e48ee 35755fb6
---
title: Add selinux module for gitlab-shell
merge_request: 4598
author:
type: fixed
......@@ -800,7 +800,7 @@ gitlab_rails['object_store']['objects']['pages']['bucket'] = nil
# gitlab_workhorse['ha'] = false
# gitlab_workhorse['listen_network'] = "unix"
# gitlab_workhorse['listen_umask'] = 000
# gitlab_workhorse['listen_addr'] = "/var/opt/gitlab/gitlab-workhorse/socket"
# gitlab_workhorse['listen_addr'] = "/var/opt/gitlab/gitlab-workhorse/sockets/socket"
# gitlab_workhorse['auth_backend'] = "http://localhost:8080"
 
##! the empty string is the default in gitlab-workhorse option parser
......
......@@ -615,7 +615,8 @@ default['gitlab']['gitlab-workhorse']['enable'] = false
default['gitlab']['gitlab-workhorse']['ha'] = false
default['gitlab']['gitlab-workhorse']['listen_network'] = "unix"
default['gitlab']['gitlab-workhorse']['listen_umask'] = 000
default['gitlab']['gitlab-workhorse']['listen_addr'] = "/var/opt/gitlab/gitlab-workhorse/socket"
default['gitlab']['gitlab-workhorse']['sockets_directory'] = nil
default['gitlab']['gitlab-workhorse']['listen_addr'] = nil
default['gitlab']['gitlab-workhorse']['auth_backend'] = "http://localhost:8080"
default['gitlab']['gitlab-workhorse']['auth_socket'] = nil
default['gitlab']['gitlab-workhorse']['cable_backend'] = "http://localhost:8280"
......
......@@ -19,6 +19,17 @@ module GitlabWorkhorse
class << self
def parse_variables
Gitlab['gitlab_workhorse']['auth_socket'] = nil if !auth_socket_specified? && auth_backend_specified?
user_listen_addr = Gitlab['gitlab_workhorse']['listen_addr']
Gitlab['gitlab_workhorse']['sockets_directory'] ||= '/var/opt/gitlab/gitlab-workhorse/sockets' if user_listen_addr.nil?
sockets_dir = Gitlab['gitlab_workhorse']['sockets_directory']
default_network = Gitlab['node']['gitlab']['gitlab-workhorse']['listen_network']
user_network = Gitlab['gitlab_workhorse']['listen_network']
network = user_network || default_network
Gitlab['gitlab_workhorse']['listen_addr'] ||= File.join(sockets_dir, 'socket') if network == "unix"
end
 
def parse_secrets
......
require_relative 'base_helper'
class GitlabWorkhorseHelper < BaseHelper
attr_reader :node
def unix_socket?
node['gitlab']['gitlab-workhorse']['listen_network'] == "unix"
end
end
......@@ -14,14 +14,15 @@ class WebServerHelper
end
 
def internal_api_url(node)
workhorse_helper = GitlabWorkhorseHelper.new(node)
gitlab_url = node['gitlab']['gitlab-rails']['internal_api_url']
 
# If no internal_api_url is specified, default to Workhorse settings
use_socket = node['gitlab']['gitlab-workhorse']['listen_network'] == "unix"
workhorse_url = node['gitlab']['gitlab-workhorse']['listen_addr']
relative_path = Gitlab['gitlab_workhorse']['relative_url']
gitlab_url ||= use_socket ? "http+unix://#{ERB::Util.url_encode(workhorse_url)}" : "http://#{workhorse_url}#{relative_path}"
gitlab_relative_path = relative_path || '' if use_socket
gitlab_url ||= workhorse_helper.unix_socket? ? "http+unix://#{ERB::Util.url_encode(workhorse_url)}" : "http://#{workhorse_url}#{relative_path}"
gitlab_relative_path = relative_path || '' if workhorse_helper.unix_socket?
 
[gitlab_url, gitlab_relative_path]
end
......
......@@ -15,6 +15,8 @@
# limitations under the License.
#
 
workhorse_helper = GitlabWorkhorseHelper.new(node)
# If nginx is disabled we will use workhorse for the healthcheck
if node['gitlab']['nginx']['enable']
listen_https = node['gitlab']['nginx']['listen_https']
......@@ -26,7 +28,7 @@ if node['gitlab']['nginx']['enable']
else
# Always use http for workhorse
schema = 'http'
use_socket = node['gitlab']['gitlab-workhorse']['listen_network'] == "unix"
use_socket = workhorse_helper.unix_socket?
host = use_socket ? 'localhost' : node['gitlab']['gitlab-workhorse']['listen_addr']
end
 
......
......@@ -343,6 +343,7 @@ templatesymlink "Create a gitlab_shell_secret and create a symlink to Rails root
sensitive true
variables(secret_token: node['gitlab']['gitlab-shell']['secret_token'])
dependent_services.each { |svc| notifies :restart, svc }
notifies :run, 'bash[Set proper security context on ssh files for selinux]', :delayed if SELinuxHelper.enabled?
end
 
gitlab_pages_services = dependent_services
......
......@@ -72,6 +72,7 @@ templatesymlink "Create a config.yml and create a symlink to Rails root" do
custom_hooks_dir: node['gitlab']['gitlab-shell']['custom_hooks_dir'],
migration: node['gitlab']['gitlab-shell']['migration'],
})
notifies :run, 'bash[Set proper security context on ssh files for selinux]', :delayed if SELinuxHelper.enabled?
end
 
link File.join(gitlab_shell_dir, ".gitlab_shell_secret") do
......@@ -83,4 +84,5 @@ file authorized_keys do
group git_group
mode '600'
action :create_if_missing
notifies :run, 'bash[Set proper security context on ssh files for selinux]', :delayed if SELinuxHelper.enabled?
end
......@@ -16,11 +16,13 @@
#
account_helper = AccountHelper.new(node)
redis_helper = RedisHelper.new(node)
workhorse_helper = GitlabWorkhorseHelper.new(node)
 
working_dir = node['gitlab']['gitlab-workhorse']['dir']
log_directory = node['gitlab']['gitlab-workhorse']['log_directory']
gitlab_workhorse_static_etc_dir = "/opt/gitlab/etc/gitlab-workhorse"
workhorse_env_dir = node['gitlab']['gitlab-workhorse']['env_directory']
gitlab_workhorse_socket_dir = node['gitlab']['gitlab-workhorse']['sockets_directory']
 
directory working_dir do
owner account_helper.gitlab_user
......@@ -29,6 +31,16 @@ directory working_dir do
recursive true
end
 
if workhorse_helper.unix_socket? && !gitlab_workhorse_socket_dir.nil?
directory gitlab_workhorse_socket_dir do
owner account_helper.gitlab_user
group account_helper.web_server_group
mode '0750'
notifies :restart, "runit_service[gitlab-workhorse]"
recursive true
end
end
directory log_directory do
owner account_helper.gitlab_user
mode '0700'
......@@ -83,4 +95,5 @@ template config_file_path do
mode "0640"
variables(object_store: object_store, object_store_provider: object_store_provider, redis_url: redis_url, password: redis_password, sentinels: redis_sentinels, sentinel_master: redis_sentinel_master, master_password: redis_sentinel_master_password)
notifies :restart, "runit_service[gitlab-workhorse]"
notifies :run, 'bash[Set proper security context on ssh files for selinux]', :delayed if SELinuxHelper.enabled?
end
......@@ -27,12 +27,19 @@ if RedhatHelper.system_is_rhel7? || RedhatHelper.system_is_rhel8?
not_if "getenforce | grep Disabled"
not_if "semodule -l | grep '^#{authorized_keys_module}\\s'"
end
gitlab_shell_module = 'gitlab-13.5.0-gitlab-shell'
execute "semodule -i /opt/gitlab/embedded/selinux/rhel/7/#{gitlab_shell_module}.pp" do
not_if "getenforce | grep Disabled"
not_if "semodule -l | grep '^#{gitlab_shell_module}\\s'"
end
end
 
# If SELinux is enabled, make sure that OpenSSH thinks the .ssh directory and authorized_keys file of the
# git_user is valid.
bash "Set proper security context on ssh files for selinux" do
code SELinuxHelper.commands(node)
code lazy { SELinuxHelper.commands(node) }
only_if "id -Z"
not_if { !node['gitlab']['gitlab-rails']['enable'] }
action :nothing
end
......@@ -8,6 +8,13 @@ exec 2>&1
 
cd <%= node['gitlab']['gitlab-workhorse']['dir'] %>
 
<% if File.exist?('/var/opt/gitlab/gitlab-workhorse/socket') %>
if [ -e "/var/opt/gitlab/gitlab-workhorse/socket" ]; then
echo "Removing orphaned workhorse socket at '/var/opt/gitlab/gitlab-workhorse/socket'"
rm /var/opt/gitlab/gitlab-workhorse/socket
fi
<% end %>
exec chpst -e /opt/gitlab/etc/gitlab-workhorse/env -P \
-U <%= node['gitlab']['user']['username'] %>:<%= node['gitlab']['user']['group'] %> \
-u <%= node['gitlab']['user']['username'] %>:<%= node['gitlab']['user']['group'] %> \
......
require_relative '../helpers/shell_out_helper'
class SELinuxHelper
class << self
include ShellOutHelper
def commands(node)
ssh_dir = File.join(node['gitlab']['user']['home'], ".ssh")
authorized_keys = node['gitlab']['gitlab-shell']['auth_file']
......@@ -8,28 +12,30 @@ class SELinuxHelper
gitlab_rails_dir = node['gitlab']['gitlab-rails']['dir']
gitlab_rails_etc_dir = File.join(gitlab_rails_dir, "etc")
gitlab_shell_secret_file = File.join(gitlab_rails_etc_dir, 'gitlab_shell_secret')
gitlab_workhorse_sockets_directory = node['gitlab']['gitlab-workhorse']['sockets_directory']
 
# If SELinux is enabled, make sure that OpenSSH thinks the .ssh directory and authorized_keys file of the
# git_user is valid.
selinux_code = []
if File.exist?(ssh_dir)
selinux_code << "semanage fcontext -a -t ssh_home_t '#{ssh_dir}(/.*)?'"
selinux_code << "restorecon -R -v '#{ssh_dir}'"
end
selinux_code << "semanage fcontext -a -t gitlab_shell_t '#{ssh_dir}(/.*)?'"
selinux_code << "restorecon -R -v '#{ssh_dir}'" if File.exist?(ssh_dir)
[
authorized_keys,
gitlab_shell_config_file,
gitlab_shell_secret_file
gitlab_shell_secret_file,
gitlab_workhorse_sockets_directory
].each do |file|
selinux_code << "semanage fcontext -a -t gitlab_shell_t '#{file}'"
next unless File.exist?(file)
 
selinux_code << "semanage fcontext -a -t ssh_home_t '#{file}'"
selinux_code << "restorecon -v '#{file}'"
end
 
selinux_code.join("\n")
end
def enabled?
success?('id -Z')
end
end
end
module gitlab-13.5.0-gitlab-shell 1.0;
type gitlab_shell_t;
require {
type sshd_t;
attribute file_type;
class sock_file write;
class file { open read getattr };
}
typeattribute gitlab_shell_t file_type;
allow sshd_t gitlab_shell_t:file read;
allow sshd_t gitlab_shell_t:file open;
allow sshd_t gitlab_shell_t:file getattr;
allow sshd_t gitlab_shell_t:sock_file write;
......@@ -144,7 +144,7 @@ RSpec.describe 'gitaly' do
 
it 'populates gitaly config.toml with gitlab-workhorse socket' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsocket'\s+relative_url_root = ''})
.with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket'\s+relative_url_root = ''})
end
end
 
......@@ -488,13 +488,37 @@ RSpec.describe 'gitaly' do
end
 
context 'with a non-default workhorse unix socket' do
before do
stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
context 'with only a listen address set' do
before do
stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
end
it 'create config file with provided values' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Ffake%2Fworkhorse%2Fsocket'\s+relative_url_root = ''})
end
end
 
it 'create config file with provided values' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Ffake%2Fworkhorse%2Fsocket'\s+relative_url_root = ''})
context 'with only a socket directory set' do
before do
stub_gitlab_rb(gitlab_workhorse: { sockets_directory: '/fake/workhorse/sockets' })
end
it 'create config file with provided values' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Ffake%2Fworkhorse%2Fsockets%2Fsocket'\s+relative_url_root = ''})
end
end
context 'with a listen_address and a sockets_directory set' do
before do
stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/sockets/in/the/wind', sockets_directory: '/sockets/in/the' })
end
it 'create config file with provided values' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fsockets%2Fin%2Fthe%2Fwind'\s+relative_url_root = ''})
end
end
end
 
......@@ -524,7 +548,7 @@ RSpec.describe 'gitaly' do
 
it 'create config file with the relative_url_root set' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsocket'\s+relative_url_root = '/gitlab'})
.with_content(%r{\[gitlab\]\s+url = 'http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket'\s+relative_url_root = '/gitlab'})
end
end
end
......
......@@ -46,7 +46,7 @@ RSpec.describe 'gitlab::gitlab-healthcheck' do
expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
.with_content(%r{url='http://localhost/help'})
expect(chef_run).to render_file("/opt/gitlab/etc/gitlab-healthcheck-rc")
.with_content(%r{flags='--unix-socket /var/opt/gitlab/gitlab-workhorse/socket'})
.with_content(%r{flags='--unix-socket /var/opt/gitlab/gitlab-workhorse/sockets/socket'})
end
 
it 'correctly renders healthcheck-rc file using workhorse on a port' do
......
......@@ -49,7 +49,7 @@ RSpec.describe 'gitlab::gitlab-shell' do
log_format: "json",
custom_hooks_dir: nil,
migration: { enabled: true, features: [] },
gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsocket',
gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
gitlab_relative_path: ''
)
)
......@@ -182,17 +182,34 @@ RSpec.describe 'gitlab::gitlab-shell' do
end
 
context 'with a non-default workhorse unix socket' do
before do
stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
context 'without sockets_directory defined' do
before do
stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
end
it 'create config file with provided values' do
expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
hash_including(
gitlab_url: 'http+unix://%2Ffake%2Fworkhorse%2Fsocket',
gitlab_relative_path: ''
)
)
end
end
 
it 'create config file with provided values' do
expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
hash_including(
gitlab_url: 'http+unix://%2Ffake%2Fworkhorse%2Fsocket',
gitlab_relative_path: ''
context 'with sockets_directory defined' do
before do
stub_gitlab_rb(gitlab_workhorse: { 'sockets_directory': '/fake/workhorse/sockets/' })
end
it 'create config file with provided values' do
expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
hash_including(
gitlab_url: 'http+unix://%2Ffake%2Fworkhorse%2Fsockets%2Fsocket',
gitlab_relative_path: ''
)
)
)
end
end
end
 
......@@ -225,7 +242,7 @@ RSpec.describe 'gitlab::gitlab-shell' do
it 'create config file with provided values' do
expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
hash_including(
gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsocket',
gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
gitlab_relative_path: '/gitlab'
)
)
......
......@@ -39,6 +39,16 @@ RSpec.describe 'gitlab::gitlab-workhorse' do
end
end
 
context 'when the deprecated socket file exists' do
it 'includes a cleanup for the orphan socket' do
allow(File).to receive(:exist?).and_call_original
allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-workhorse/socket').and_return(true)
expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-workhorse/run").with_content { |content|
expect(content).to match(%r(Removing orphaned workhorse socket at))
}
end
end
context 'user and group' do
context 'default values' do
it_behaves_like "enabled runit service", "gitlab-workhorse", "root", "root"
......
......@@ -2,37 +2,42 @@ require 'chef_helper'
 
RSpec.describe 'gitlab::gitlab-selinux' do
let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink storage_directory)).converge('gitlab::default') }
let(:templatesymlink) { chef_run.templatesymlink('Create a config.yml and create a symlink to Rails root') }
 
before do
allow(Gitlab).to receive(:[]).and_call_original
stub_default_should_notify?(true)
end
 
context 'when NOT running on selinux' do
before { stub_command('id -Z').and_return(false) }
before do
allow_any_instance_of(ShellOutHelper).to receive(:success?).with('id -Z').and_return(false)
end
 
it 'should not run the semanage bash command' do
expect(chef_run).not_to run_bash('Set proper security context on ssh files for selinux')
expect(templatesymlink).to_not notify('bash[Set proper security context on ssh files for selinux]').delayed
end
end
 
context 'when running on selinux' do
before do
stub_command('id -Z').and_return('')
allow_any_instance_of(ShellOutHelper).to receive(:success?).with('id -Z').and_return(true)
allow(File).to receive(:exist?).and_call_original
allow(File).to receive(:exist?).with('/var/opt/gitlab/.ssh').and_return(true)
allow(File).to receive(:exist?).with('/var/opt/gitlab/.ssh/authorized_keys').and_return(true)
allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret').and_return(true)
allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-shell/config.yml').and_return(true)
allow(File).to receive(:exist?).with('/var/opt/gitlab/gitlab-workhorse/sockets').and_return(true)
end
 
let(:bash_block) { chef_run.bash('Set proper security context on ssh files for selinux') }
 
def semanage_fcontext(filename)
"semanage fcontext -a -t ssh_home_t '#{filename}'"
"semanage fcontext -a -t gitlab_shell_t '#{filename}'"
end
 
it 'should run the semanage bash command' do
expect(chef_run).to run_bash('Set proper security context on ssh files for selinux')
expect(templatesymlink).to notify('bash[Set proper security context on ssh files for selinux]').delayed
end
 
it 'sets the security context of gitlab-shell files' do
......@@ -40,7 +45,8 @@ RSpec.describe 'gitlab::gitlab-selinux' do
files = %w(/var/opt/gitlab/.ssh(/.*)?
/var/opt/gitlab/.ssh/authorized_keys
/var/opt/gitlab/gitlab-shell/config.yml
/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret)
/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret
/var/opt/gitlab/gitlab-workhorse/sockets)
managed_files = files.map { |file| semanage_fcontext(file) }
 
expect(lines).to include(*managed_files)
......@@ -48,6 +54,29 @@ RSpec.describe 'gitlab::gitlab-selinux' do
expect(lines).to include("restorecon -v '/var/opt/gitlab/.ssh/authorized_keys'")
expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-shell/config.yml'")
expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret'")
expect(lines).to include("restorecon -v '/var/opt/gitlab/gitlab-workhorse/sockets'")
end
context 'and the user configured a custom workhorse sockets directory' do
let(:user_sockets_directory) { '/how/do/you/do' }
before do
stub_gitlab_rb(
gitlab_workhorse: {
listen_network: 'unix',
sockets_directory: user_sockets_directory
}
)
end
it 'sets the security context of a custom workhorse sockets directory' do
allow(File).to receive(:exist?).with(user_sockets_directory).and_return(true)
lines = bash_block.code.split("\n")
files = [user_sockets_directory]
managed_files = files.map { |file| semanage_fcontext(file) }
expect(lines).to include(*managed_files)
expect(lines).to include("restorecon -v '#{user_sockets_directory}'")
end
end
 
context 'when gitlab-rails is disabled' do
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment