# HG changeset patch
# User Balasankar 'Balu' C <balasankar@gitlab.com>
# Date 1701294444 0
#      Wed Nov 29 21:47:24 2023 +0000
# Node ID a978e3430da0cf17ca889776cee3c3b306c087c7
# Parent  f15c34848a31670455a8f82e7814f374ee8cebeb
Support specifying encrypted secrets path for Redis

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

Changelog: added

Signed-off-by: Balasankar "Balu" C <balasankar@gitlab.com>

diff --git a/files/gitlab-cookbooks/gitlab/attributes/default.rb b/files/gitlab-cookbooks/gitlab/attributes/default.rb
--- a/files/gitlab-cookbooks/gitlab/attributes/default.rb
+++ b/files/gitlab-cookbooks/gitlab/attributes/default.rb
@@ -446,6 +446,7 @@
 default['gitlab']['gitlab_rails']['redis_tls_client_cert_file'] = nil
 default['gitlab']['gitlab_rails']['redis_tls_client_key_file'] = nil
 default['gitlab']['gitlab_rails']['redis_password'] = nil
+default['gitlab']['gitlab_rails']['redis_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_socket'] = "/var/opt/gitlab/redis/redis.socket"
 default['gitlab']['gitlab_rails']['redis_enable_client'] = true
 default['gitlab']['gitlab_rails']['redis_sentinels'] = []
@@ -455,6 +456,7 @@
 default['gitlab']['gitlab_rails']['redis_cache_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_cache_username'] = nil
 default['gitlab']['gitlab_rails']['redis_cache_password'] = nil
+default['gitlab']['gitlab_rails']['redis_cache_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_cache_cluster_nodes'] = []
 default['gitlab']['gitlab_rails']['redis_cache_ssl'] = false
 default['gitlab']['gitlab_rails']['redis_cache_tls_ca_cert_dir'] = default['gitlab']['gitlab_rails']['redis_tls_ca_cert_dir'].dup
@@ -464,6 +466,7 @@
 default['gitlab']['gitlab_rails']['redis_queues_instance'] = nil
 default['gitlab']['gitlab_rails']['redis_queues_username'] = nil
 default['gitlab']['gitlab_rails']['redis_queues_password'] = nil
+default['gitlab']['gitlab_rails']['redis_queues_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_queues_sentinels'] = []
 default['gitlab']['gitlab_rails']['redis_queues_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_queues_cluster_nodes'] = []
@@ -477,6 +480,7 @@
 default['gitlab']['gitlab_rails']['redis_shared_state_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_shared_state_username'] = nil
 default['gitlab']['gitlab_rails']['redis_shared_state_password'] = nil
+default['gitlab']['gitlab_rails']['redis_shared_state_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_shared_state_cluster_nodes'] = []
 default['gitlab']['gitlab_rails']['redis_shared_state_ssl'] = false
 default['gitlab']['gitlab_rails']['redis_shared_state_tls_ca_cert_dir'] = default['gitlab']['gitlab_rails']['redis_tls_ca_cert_dir'].dup
@@ -488,6 +492,7 @@
 default['gitlab']['gitlab_rails']['redis_trace_chunks_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_trace_chunks_username'] = nil
 default['gitlab']['gitlab_rails']['redis_trace_chunks_password'] = nil
+default['gitlab']['gitlab_rails']['redis_trace_chunks_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_trace_chunks_cluster_nodes'] = []
 default['gitlab']['gitlab_rails']['redis_trace_chunks_ssl'] = false
 default['gitlab']['gitlab_rails']['redis_trace_chunks_tls_ca_cert_dir'] = default['gitlab']['gitlab_rails']['redis_tls_ca_cert_dir'].dup
@@ -499,6 +504,7 @@
 default['gitlab']['gitlab_rails']['redis_actioncable_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_actioncable_username'] = nil
 default['gitlab']['gitlab_rails']['redis_actioncable_password'] = nil
+default['gitlab']['gitlab_rails']['redis_actioncable_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_actioncable_cluster_nodes'] = []
 default['gitlab']['gitlab_rails']['redis_actioncable_ssl'] = false
 default['gitlab']['gitlab_rails']['redis_actioncable_tls_ca_cert_dir'] = default['gitlab']['gitlab_rails']['redis_tls_ca_cert_dir'].dup
@@ -510,6 +516,7 @@
 default['gitlab']['gitlab_rails']['redis_rate_limiting_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_rate_limiting_username'] = nil
 default['gitlab']['gitlab_rails']['redis_rate_limiting_password'] = nil
+default['gitlab']['gitlab_rails']['redis_rate_limiting_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_rate_limiting_cluster_nodes'] = []
 default['gitlab']['gitlab_rails']['redis_rate_limiting_ssl'] = false
 default['gitlab']['gitlab_rails']['redis_rate_limiting_tls_ca_cert_dir'] = default['gitlab']['gitlab_rails']['redis_tls_ca_cert_dir'].dup
@@ -521,6 +528,7 @@
 default['gitlab']['gitlab_rails']['redis_sessions_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_sessions_username'] = nil
 default['gitlab']['gitlab_rails']['redis_sessions_password'] = nil
+default['gitlab']['gitlab_rails']['redis_sessions_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_sessions_cluster_nodes'] = []
 default['gitlab']['gitlab_rails']['redis_sessions_ssl'] = false
 default['gitlab']['gitlab_rails']['redis_sessions_tls_ca_cert_dir'] = default['gitlab']['gitlab_rails']['redis_tls_ca_cert_dir'].dup
@@ -532,6 +540,7 @@
 default['gitlab']['gitlab_rails']['redis_repository_cache_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_repository_cache_username'] = nil
 default['gitlab']['gitlab_rails']['redis_repository_cache_password'] = nil
+default['gitlab']['gitlab_rails']['redis_repository_cache_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_repository_cache_cluster_nodes'] = []
 default['gitlab']['gitlab_rails']['redis_repository_cache_ssl'] = false
 default['gitlab']['gitlab_rails']['redis_repository_cache_tls_ca_cert_dir'] = default['gitlab']['gitlab_rails']['redis_tls_ca_cert_dir'].dup
@@ -543,6 +552,7 @@
 default['gitlab']['gitlab_rails']['redis_cluster_rate_limiting_sentinels_password'] = nil
 default['gitlab']['gitlab_rails']['redis_cluster_rate_limiting_username'] = nil
 default['gitlab']['gitlab_rails']['redis_cluster_rate_limiting_password'] = nil
+default['gitlab']['gitlab_rails']['redis_cluster_rate_limiting_encrypted_settings_file'] = nil
 default['gitlab']['gitlab_rails']['redis_cluster_rate_limiting_cluster_nodes'] = []
 default['gitlab']['gitlab_rails']['redis_cluster_rate_limiting_ssl'] = false
 default['gitlab']['gitlab_rails']['redis_cluster_rate_limiting_tls_ca_cert_dir'] = default['gitlab']['gitlab_rails']['redis_tls_ca_cert_dir'].dup
diff --git a/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb b/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb
--- a/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb
+++ b/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb
@@ -17,6 +17,7 @@
 require_relative 'nginx.rb'
 require_relative '../../gitaly/libraries/gitaly.rb'
 require_relative '../../package/libraries/settings_dsl.rb'
+require_relative 'redis_helper'
 
 module GitlabRails
   ALLOWED_DATABASES = %w[main ci geo embedding].freeze
@@ -323,7 +324,16 @@
 
     def parse_encrypted_settings_path
       # This requires the parse_shared_dir to be executed before
-      Gitlab['gitlab_rails']['encrypted_settings_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'encrypted_settings')
+      encrypted_settings_path = Gitlab['gitlab_rails']['encrypted_settings_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'encrypted_settings')
+
+      RedisHelper::REDIS_INSTANCES.each do |instance|
+        Gitlab['gitlab_rails']["redis_#{instance}_encrypted_settings_file"] ||= Gitlab['gitlab_rails']['redis_encrypted_settings_file'] || File.join(encrypted_settings_path, "redis.#{instance}.yml.enc")
+      end
+
+      # NOTE: The default value of `redis_encrypted_settings_file` should be
+      # set only after the instance-specific ones are handled, or this default
+      # value will get used for the instance-specific settings.
+      Gitlab['gitlab_rails']['redis_encrypted_settings_file'] ||= File.join(encrypted_settings_path, 'redis.yml.enc')
     end
 
     def parse_pages_dir
diff --git a/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb b/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb
--- a/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb
+++ b/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb
@@ -217,6 +217,7 @@
 redis_tls_ca_cert_file = node['gitlab']['gitlab_rails']['redis_tls_ca_cert_file']
 redis_tls_client_cert_file = node['gitlab']['gitlab_rails']['redis_tls_client_cert_file']
 redis_tls_client_key_file = node['gitlab']['gitlab_rails']['redis_tls_client_key_file']
+redis_encrypted_settings_file = node['gitlab']['gitlab_rails']['redis_encrypted_settings_file']
 
 templatesymlink "Create a secrets.yml and create a symlink to Rails root" do
   link_from File.join(gitlab_rails_source_dir, "config/secrets.yml")
@@ -253,7 +254,8 @@
     redis_tls_ca_cert_dir: redis_tls_ca_cert_dir,
     redis_tls_ca_cert_file: redis_tls_ca_cert_file,
     redis_tls_client_cert_file: redis_tls_client_cert_file,
-    redis_tls_client_key_file: redis_tls_client_key_file
+    redis_tls_client_key_file: redis_tls_client_key_file,
+    redis_encrypted_settings_file: redis_encrypted_settings_file
   )
   dependent_services.each { |svc| notifies :restart, svc }
   sensitive true
@@ -305,6 +307,7 @@
   ca_cert_file = node['gitlab']['gitlab_rails']["redis_#{instance}_tls_ca_cert_file"]
   certificate_file = node['gitlab']['gitlab_rails']["redis_#{instance}_tls_client_cert_file"]
   key_file = node['gitlab']['gitlab_rails']["redis_#{instance}_tls_client_key_file"]
+  instance_encrypted_settings_file = node['gitlab']['gitlab_rails']["redis_#{instance}_encrypted_settings_file"]
   from_filename = File.join(gitlab_rails_source_dir, "config/#{filename}")
   to_filename = File.join(gitlab_rails_etc_dir, filename)
 
@@ -329,7 +332,8 @@
       redis_tls_ca_cert_dir: ca_cert_dir,
       redis_tls_ca_cert_file: ca_cert_file,
       redis_tls_client_cert_file: certificate_file,
-      redis_tls_client_key_file: key_file
+      redis_tls_client_key_file: key_file,
+      redis_encrypted_settings_file: instance_encrypted_settings_file
     )
     dependent_services.each { |svc| notifies :restart, svc }
     action :delete if url.nil? && clusters.empty?
diff --git a/files/gitlab-cookbooks/gitlab/templates/default/resque.yml.erb b/files/gitlab-cookbooks/gitlab/templates/default/resque.yml.erb
--- a/files/gitlab-cookbooks/gitlab/templates/default/resque.yml.erb
+++ b/files/gitlab-cookbooks/gitlab/templates/default/resque.yml.erb
@@ -13,6 +13,7 @@
     <% end %>
   <% else %>
   url: <%= @redis_url %>
+  secret_file: <%= @redis_encrypted_settings_file %>
   <%- if @redis_ssl %>
   ssl_params:
     <%- if @redis_tls_ca_cert_dir %>ca_path: "<%= @redis_tls_ca_cert_dir %>"<% end %>
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails_spec.rb
--- a/spec/chef/cookbooks/gitlab/recipes/gitlab-rails_spec.rb
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-rails_spec.rb
@@ -440,7 +440,8 @@
               redis_tls_ca_cert_dir: "/opt/gitlab/embedded/ssl/certs/",
               redis_tls_ca_cert_file: "/opt/gitlab/embedded/ssl/certs/cacert.pem",
               redis_tls_client_cert_file: nil,
-              redis_tls_client_key_file: nil
+              redis_tls_client_key_file: nil,
+              redis_encrypted_settings_file: "/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.#{instance}.yml.enc"
             )
 
             expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml").with_content { |content|
@@ -482,7 +483,8 @@
               redis_tls_ca_cert_dir: "/opt/gitlab/embedded/ssl/certs/",
               redis_tls_ca_cert_file: "/opt/gitlab/embedded/ssl/certs/cacert.pem",
               redis_tls_client_cert_file: nil,
-              redis_tls_client_key_file: nil
+              redis_tls_client_key_file: nil,
+              redis_encrypted_settings_file: "/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.#{instance}.yml.enc"
             )
 
             expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml").with_content { |content|
@@ -524,7 +526,8 @@
                 redis_tls_ca_cert_dir: "/opt/gitlab/embedded/ssl/certs/",
                 redis_tls_ca_cert_file: "/opt/gitlab/embedded/ssl/certs/cacert.pem",
                 redis_tls_client_cert_file: nil,
-                redis_tls_client_key_file: nil
+                redis_tls_client_key_file: nil,
+                redis_encrypted_settings_file: "/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.#{instance}.yml.enc"
               )
 
               expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml").with_content { |content|
@@ -545,6 +548,42 @@
           expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root')
         end
       end
+
+      describe 'encrypted_settings_file' do
+        cached(:chef_run) do
+          ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default')
+        end
+
+        let(:cache_secret_file) { '/etc/gitlab/cache.redis.enc' }
+        let(:global_secret_file) { '/etc/gitlab/global.redis.enc' }
+
+        context 'with separate file for an instance' do
+          before do
+            stub_gitlab_rb(
+              gitlab_rails: {
+                redis_encrypted_settings_file: global_secret_file,
+                redis_cache_instance: 'redis://redis.cache.instance',
+                redis_cache_encrypted_settings_file: cache_secret_file,
+                redis_shared_state_instance: 'redis://redis.shared_state.instance'
+              }
+            )
+          end
+
+          it 'uses specified path for the cache instance' do
+            expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.cache.yml").with_content { |content|
+              generated_yml = YAML.safe_load(content)
+              expect(generated_yml.dig('production', 'secret_file')).to eq(cache_secret_file)
+            }
+          end
+
+          it 'uses global path for the shared state instance' do
+            expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.shared_state.yml").with_content { |content|
+              generated_yml = YAML.safe_load(content)
+              expect(generated_yml.dig('production', 'secret_file')).to eq(global_secret_file)
+            }
+          end
+        end
+      end
     end
 
     context 'redis.yml override' do