# HG changeset patch
# User Balasankar 'Balu' C <balasankar@gitlab.com>
# Date 1726603624 0
#      Tue Sep 17 20:07:04 2024 +0000
# Node ID b10ba2458230fe99dfdcfa3e5adf29ea3930681d
# Parent  872d4e892c2f1b8a8e607f1dca5762edb61cb888
Add Gitaly authentication token attribute

Rather than re-use the attribute from GitLab Shell, Gitaly stores
the token it uses to authenticate with GitLab under a unique attribute.

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

Changelog: changed

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

diff --git a/files/gitlab-cookbooks/gitaly/attributes/default.rb b/files/gitlab-cookbooks/gitaly/attributes/default.rb
--- a/files/gitlab-cookbooks/gitaly/attributes/default.rb
+++ b/files/gitlab-cookbooks/gitaly/attributes/default.rb
@@ -22,5 +22,8 @@
     use_bundled_binaries: true,
     bin_path: "#{node['package']['install-dir']}/embedded/bin/git"
   },
-  storage: []
+  storage: [],
+  gitlab: {
+    secret_file: "#{node['gitaly']['dir']}/.gitlab_secret"
+  }
 }
diff --git a/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb b/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb
--- a/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb
+++ b/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb
@@ -30,6 +30,16 @@
       check_duplicate_storage_paths
     end
 
+    def parse_secrets
+      # The secret should be same between GitLab Rails, GitLab Shell, and
+      # Gitaly. GitLab Shell has a priority of 10, which means it gets parsed
+      # before Gitaly and Gitlab['gitlab_shell']['secret_token'] will
+      # definitely have a value.
+      Gitlab['gitaly']['gitlab_secret'] ||= Gitlab['gitlab_shell']['secret_token']
+
+      LoggingHelper.warning("Gitaly and GitLab Shell specifies different secrets to authenticate with GitLab") if Gitlab['gitaly']['gitlab_secret'] != Gitlab['gitlab_shell']['secret_token']
+    end
+
     def gitaly_address
       listen_addr = user_config.dig('configuration', 'listen_addr')         || package_default.dig('configuration', 'listen_addr')
       socket_path = user_config.dig('configuration', 'socket_path')         || package_default.dig('configuration', 'socket_path')
diff --git a/files/gitlab-cookbooks/gitaly/recipes/enable.rb b/files/gitlab-cookbooks/gitaly/recipes/enable.rb
--- a/files/gitlab-cookbooks/gitaly/recipes/enable.rb
+++ b/files/gitlab-cookbooks/gitaly/recipes/enable.rb
@@ -89,6 +89,16 @@
 
 gitlab_url, gitlab_relative_path = WebServerHelper.internal_api_url(node)
 
+secret_file = node['gitaly']['configuration']['gitlab']['secret_file']
+file secret_file do
+  owner "root"
+  group "root"
+  mode "0644"
+  sensitive true
+  content node['gitaly']['gitlab_secret']
+  notifies :restart, 'runit_service[gitaly]' if omnibus_helper.should_notify?('gitaly')
+end
+
 template "Create Gitaly config.toml" do
   path config_path
   source "gitaly-config.toml.erb"
diff --git a/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb b/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb
--- a/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb
+++ b/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb
@@ -120,6 +120,9 @@
         'incoming_email_auth_token' => Gitlab['mailroom']['incoming_email_auth_token'],
         'service_desk_email_auth_token' => Gitlab['mailroom']['service_desk_email_auth_token'],
       },
+      'gitaly' => {
+        'gitlab_secret' => Gitlab['gitaly']['gitlab_secret']
+      }
     }
 
     if Gitlab['mattermost']['gitlab_enable']
diff --git a/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb b/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb
--- a/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb
+++ b/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb
@@ -64,8 +64,10 @@
   let(:pack_objects_cache_enabled) { true }
   let(:pack_objects_cache_dir) { '/pack-objects-cache' }
   let(:pack_objects_cache_max_age) { '10m' }
+
   before do
     allow(Gitlab).to receive(:[]).and_call_original
+    allow(SecretsHelper).to receive(:generate_hex).and_return('4ecd22c031fee5c7368a5a102f76dc41')
   end
 
   context 'by default' do
@@ -84,6 +86,10 @@
       expect(chef_run.version_file('Create version file for Gitaly')).to notify('runit_service[gitaly]').to(:hup)
     end
 
+    it 'creates secret file for authenticating with GitLab' do
+      expect(chef_run).to create_file('/var/opt/gitlab/gitaly/.gitlab_secret').with_content('4ecd22c031fee5c7368a5a102f76dc41')
+    end
+
     it 'populates gitaly config.toml with defaults' do
       expect(get_rendered_toml(chef_run, '/var/opt/gitlab/gitaly/config.toml')).to eq(
         {
@@ -95,7 +101,8 @@
           },
           gitlab: {
             relative_url_root: '',
-            url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket'
+            url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
+            secret_file: '/var/opt/gitlab/gitaly/.gitlab_secret'
           },
           'gitlab-shell': {
             dir: '/opt/gitlab/embedded/service/gitlab-shell'
@@ -132,6 +139,56 @@
     end
   end
 
+  context 'with a user specified GitLab secret' do
+    context 'when the Gitlab Shell and Gitaly secrets match' do
+      before do
+        stub_gitlab_rb(
+          gitaly: {
+            gitlab_secret: 'my-super-secret-password'
+          },
+          gitlab_shell: {
+            secret_token: 'my-super-secret-password'
+          }
+        )
+      end
+
+      it 'populates the secret file with specified value' do
+        expect(chef_run).to create_file('/var/opt/gitlab/gitaly/.gitlab_secret').with_content('my-super-secret-password')
+      end
+
+      it 'does not log a warning' do
+        expect(LoggingHelper).not_to receive(:warning).with("Gitaly and GitLab Shell specifies different secrets to authenticate with GitLab")
+
+        chef_run
+      end
+    end
+
+    context 'when the Gitlab Shell and Gitaly secrets differ' do
+      before do
+        stub_gitlab_rb(
+          gitaly: {
+            gitlab_secret: 'password-for-gitaly'
+          },
+          gitlab_shell: {
+            secret_token: 'password-for-shell'
+          }
+        )
+
+        allow(LoggingHelper).to receive(:warning).and_call_original
+      end
+
+      it 'populates the secret file with specified value' do
+        expect(chef_run).to create_file('/var/opt/gitlab/gitaly/.gitlab_secret').with_content('password-for-gitaly')
+      end
+
+      it 'logs a secret mismatch warning' do
+        expect(LoggingHelper).to receive(:warning).with("Gitaly and GitLab Shell specifies different secrets to authenticate with GitLab")
+
+        chef_run
+      end
+    end
+  end
+
   context 'log directory and runit group' do
     context 'default values' do
       it_behaves_like 'enabled logged service', 'gitaly', true, { log_directory_owner: 'git' }
@@ -425,6 +482,7 @@
           gitlab: {
             url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
             relative_url_root: '',
+            secret_file: '/var/opt/gitlab/gitaly/.gitlab_secret'
           },
           logging: {
             dir: 'overridden_logging_path',
@@ -618,7 +676,8 @@
               read_timeout: 123,
               user: 'user123'
             },
-            url: 'http://localhost:3000'
+            url: 'http://localhost:3000',
+            secret_file: '/var/opt/gitlab/gitaly/.gitlab_secret'
           },
           'gitlab-shell': {
             dir: '/opt/gitlab/embedded/service/gitlab-shell'