redis: Fix password auth with UNIX domain sockets

Previously if a Redis instance listened on a UNIX socket but a
password were set, GitLab Rails would not be able to authenticate.
This occurred because the UNIX URL doesn't contain a password.

Both Ruby and Go Redis clients support URLs in the form:

unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>

Relates to work started in
https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/2194

Changelog: fixed
This commit is contained in:
Stan Hu 2024-04-26 00:19:53 -07:00
parent 6fab34cacb
commit 656fb39a8c
No known key found for this signature in database
GPG Key ID: 8D3931AD39CC7A20
7 changed files with 95 additions and 21 deletions

View File

@ -33,13 +33,17 @@ class RedisHelper
redis_socket = gitlab_rails['redis_socket']
redis_socket = false if RedisHelper::Checks.is_gitlab_rails_redis_tcp?
params = redis_params(support_sentinel_groupname: support_sentinel_groupname)
if redis_socket && !RedisHelper::Checks.has_sentinels?
uri = URI('unix:/')
uri = URI("unix://")
uri.path = redis_socket
else
params = redis_params(support_sentinel_groupname: support_sentinel_groupname)
if params[2]
password = encode_redis_password(params[2])
uri.userinfo = ":#{password}"
end
else
uri = build_redis_url(
ssl: gitlab_rails['redis_ssl'],
host: params[0],
@ -97,6 +101,10 @@ class RedisHelper
uri
end
def encode_redis_password(password)
URI::Generic::DEFAULT_PARSER.escape(password)
end
def redis_sentinel_urls(sentinels_key)
gitlab_rails = @node['gitlab']['gitlab_rails']

View File

@ -28,5 +28,14 @@ module NewRedisHelper
)
end
end
# RFC 3986 says that the userinfo value should be percent-encoded:
# https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1.
# Note that CGI.escape and URI.encode_www_form_component encodes
# a space as "+" instead of "%20". While this appears to be handled with
# the Ruby client, the Go client doesn't work with "+".
def encode_redis_password(password)
URI::Generic::DEFAULT_PARSER.escape(password)
end
end
end

View File

@ -37,12 +37,17 @@ module NewRedisHelper
redis_socket
end
if socket && !has_sentinels?
uri = URI('unix:/')
uri.path = socket
else
params = redis_credentials
params = redis_credentials
if socket && !has_sentinels?
uri = URI("unix://")
uri.path = socket
if params[:password]
password = NewRedisHelper.encode_redis_password(params[:password])
uri.userinfo = ":#{password}"
end
else
uri = NewRedisHelper.build_redis_url(
ssl: redis_ssl,
host: params[:host],

View File

@ -49,7 +49,7 @@ RSpec.describe RedisHelper do
context '#redis_url' do
context 'with default configuration' do
it 'returns a unix socket' do
expect(subject.redis_url.to_s).to eq('unix:/var/opt/gitlab/redis/redis.socket')
expect(subject.redis_url.to_s).to eq('unix:///var/opt/gitlab/redis/redis.socket')
end
end

View File

@ -210,20 +210,24 @@ RSpec.describe 'gitlab::gitlab-rails' do
context 'with redis settings' do
let(:config_file) { '/var/opt/gitlab/gitlab-rails/etc/resque.yml' }
let(:resque_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/resque.yml') }
let(:resque_yml_file_content) { ChefSpec::Renderer.new(chef_run, resque_yml_template).content }
let(:resque_yml) { YAML.safe_load(resque_yml_file_content, aliases: true, symbolize_names: true) }
let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') }
context 'and default configuration' do
it 'creates the config file with the required redis settings' do
expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root').with_variables(
hash_including(
redis_url: URI('unix:/var/opt/gitlab/redis/redis.socket'),
redis_url: URI('unix:///var/opt/gitlab/redis/redis.socket'),
redis_sentinels: [],
redis_enable_client: true
)
)
expect(chef_run).to render_file(config_file).with_content { |content|
expect(content).to match(%r(url: unix:/var/opt/gitlab/redis/redis.socket$))
expect(content).to match(%r(url: unix:///var/opt/gitlab/redis/redis.socket$))
expect(content).not_to match(/id:/)
}
end
@ -231,14 +235,14 @@ RSpec.describe 'gitlab::gitlab-rails' do
it 'creates cable.yml with the same settings' do
expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables(
hash_including(
redis_url: URI('unix:/var/opt/gitlab/redis/redis.socket'),
redis_url: URI('unix:///var/opt/gitlab/redis/redis.socket'),
redis_sentinels: [],
redis_enable_client: true
)
)
expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/cable.yml').with_content { |content|
expect(content).to match(%r(url: unix:/var/opt/gitlab/redis/redis.socket$))
expect(content).to match(%r(url: unix:///var/opt/gitlab/redis/redis.socket$))
}
end
@ -301,10 +305,6 @@ RSpec.describe 'gitlab::gitlab-rails' do
end
context 'with TLS settings' do
let(:resque_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/resque.yml') }
let(:resque_yml_file_content) { ChefSpec::Renderer.new(chef_run, resque_yml_template).content }
let(:resque_yml) { YAML.safe_load(resque_yml_file_content, aliases: true, symbolize_names: true) }
before do
stub_gitlab_rb(
gitlab_rails: {
@ -370,6 +370,38 @@ RSpec.describe 'gitlab::gitlab-rails' do
end
end
context 'with a password and UNIX socket' do
let(:cable_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/cable.yml') }
let(:cable_yml_file_content) { ChefSpec::Renderer.new(chef_run, cable_yml_template).content }
let(:cable_yml) { YAML.safe_load(cable_yml_file_content, aliases: true, symbolize_names: true) }
before do
stub_gitlab_rb(
gitlab_rails: {
redis_password: 'my pass',
}
)
end
it 'renders resque.yml with password' do
expected_output = {
url: "unix://:my%20pass@/var/opt/gitlab/redis/redis.socket",
secret_file: "/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.yml.enc"
}
expect(resque_yml[:production]).to eq(expected_output)
end
it 'creates cable.yml with password' do
expected_output = {
adapter: 'redis',
url: "unix://:my%20pass@/var/opt/gitlab/redis/redis.socket",
}
expect(cable_yml[:production]).to eq(expected_output)
end
end
context 'with multiple instances' do
context 'with action cable' do
before do

View File

@ -504,7 +504,7 @@ RSpec.describe 'gitlab::gitlab-workhorse' do
context 'with default values for redis' do
it 'should generate config file' do
content_url = 'URL = "unix:/var/opt/gitlab/redis/redis.socket"'
content_url = 'URL = "unix:///var/opt/gitlab/redis/redis.socket"'
expect(chef_run).to render_file(config_file).with_content(content_url)
expect(chef_run).not_to render_file(config_file).with_content(/Sentinel/)
end
@ -543,7 +543,7 @@ RSpec.describe 'gitlab::gitlab-workhorse' do
end
it 'should generate config file with the specified values' do
content_url = 'URL = "unix:/home/random/path.socket"'
content_url = 'URL = "unix://:examplepassword@/home/random/path.socket"'
content_password = 'Password = "examplepassword"'
expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_password)
@ -653,7 +653,7 @@ RSpec.describe 'gitlab::gitlab-workhorse' do
end
it 'should generate config file with the specified values' do
content_url = 'URL = "unix:/home/random/path.socket"'
content_url = 'URL = "unix://:some%20pass@/home/random/path.socket"'
content_password = 'Password = "some pass"'
expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_url)
expect(chef_run).to render_file("/var/opt/gitlab/gitlab-workhorse/config.toml").with_content(content_password)

View File

@ -16,7 +16,7 @@ RSpec.describe NewRedisHelper::GitlabWorkhorse do
context 'by default' do
it 'returns information about the default Redis instance' do
expect(subject.redis_params).to eq(
url: 'unix:/var/opt/gitlab/redis/redis.socket',
url: 'unix:///var/opt/gitlab/redis/redis.socket',
password: nil,
sentinels: [],
sentinelMaster: 'gitlab-redis',
@ -26,6 +26,26 @@ RSpec.describe NewRedisHelper::GitlabWorkhorse do
end
context 'with user specified values' do
context 'when password set for UNIX socket' do
before do
stub_gitlab_rb(
gitlab_rails: {
redis_password: 'redis-password'
}
)
end
it 'returns a UNIX socket URL with password' do
expect(subject.redis_params).to eq(
url: 'unix://:redis-password@/var/opt/gitlab/redis/redis.socket',
password: 'redis-password',
sentinels: [],
sentinelMaster: 'gitlab-redis',
sentinelPassword: nil
)
end
end
context 'when settings specified via gitlab_rails' do
before do
stub_gitlab_rb(