gitlab_rails.rb 15.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Copyright:: Copyright (c) 2016 GitLab Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require_relative 'nginx.rb'
18
require_relative '../../gitaly/libraries/gitaly.rb'
19

20
module GitlabRails
21
22
  ALLOWED_DATABASES = %w[main ci geo].freeze
  MAIN_DATABASES = %w[main geo].freeze
23
  SHARED_DATABASE_ATTRIBUTES = %w[db_host db_port db_database].freeze
24

25
26
  class << self
    def parse_variables
27
      parse_database_adapter
28
29
      parse_database_settings
      parse_databases
30
31
32
      parse_external_url
      parse_directories
      parse_gitlab_trusted_proxies
33
34
      parse_incoming_email_logfile
      parse_service_desk_email_logfile
35
      parse_maximum_request_duration
36
37
38
    end

    def parse_directories
39
      parse_runtime_dir
40
41
      parse_shared_dir
      parse_artifacts_dir
42
      parse_external_diffs_dir
43
      parse_lfs_objects_dir
44
      parse_uploads_dir
45
      parse_packages_dir
46
      parse_dependency_proxy_dir
47
      parse_terraform_state_dir
Darby Frey's avatar
Darby Frey committed
48
      parse_ci_secure_files_dir
49
      parse_encrypted_settings_path
50
      parse_pages_dir
Marin Jankovski's avatar
Marin Jankovski committed
51
      parse_repository_storage
52
53
    end

54
55
56
57
    # rubocop:disable Metrics/AbcSize
    # rubocop:disable Metrics/CyclomaticComplexity
    # rubocop:disable Metrics/PerceivedComplexity
    def parse_secrets
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
      # Blow up when the existing configuration is ambiguous, so we don't accidentally throw away important secrets
      ci_db_key_base = Gitlab['gitlab_ci']['db_key_base']
      rails_db_key_base = Gitlab['gitlab_rails']['db_key_base']

      if ci_db_key_base && rails_db_key_base && ci_db_key_base != rails_db_key_base
        message = [
          "The value of Gitlab['gitlab_ci']['db_key_base'] (#{ci_db_key_base}) does not match the value of Gitlab['gitlab_rails']['db_key_base'] (#{rails_db_key_base}).",
          "Please back up both secrets, set Gitlab['gitlab_rails']['db_key_base'] to the value of Gitlab['gitlab_ci']['db_key_base'], and try again.",
          "For more information, see <https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update/README.md#migrating-legacy-secrets>"
        ]

        raise message.join("\n\n")
      end

      # Transform legacy key names to new key names
      Gitlab['gitlab_rails']['db_key_base'] ||= Gitlab['gitlab_ci']['db_key_base'] # Changed in 8.11
      Gitlab['gitlab_rails']['secret_key_base'] ||= Gitlab['gitlab_ci']['db_key_base'] # Changed in 8.11
      Gitlab['gitlab_rails']['otp_key_base'] ||= Gitlab['gitlab_rails']['secret_token']
76
      Gitlab['gitlab_rails']['openid_connect_signing_key'] ||= Gitlab['gitlab_rails']['jws_private_key'] # Changed in 10.1
77
78
79
80
81

      # Note: If you add another secret to generate here make sure it gets written to disk in SecretsHelper.write_to_gitlab_secrets
      Gitlab['gitlab_rails']['db_key_base'] ||= SecretsHelper.generate_hex(64)
      Gitlab['gitlab_rails']['secret_key_base'] ||= SecretsHelper.generate_hex(64)
      Gitlab['gitlab_rails']['otp_key_base'] ||= SecretsHelper.generate_hex(64)
82
      Gitlab['gitlab_rails']['encrypted_settings_key_base'] ||= SecretsHelper.generate_hex(64)
83
      Gitlab['gitlab_rails']['openid_connect_signing_key'] ||= SecretsHelper.generate_rsa(4096).to_pem
84
85
86
87
88
89

      Gitlab['gitlab_rails']['initial_root_password'] = ENV['GITLAB_ROOT_PASSWORD'] || Gitlab['gitlab_rails']['initial_root_password']
      if Gitlab['gitlab_rails']['initial_root_password'].nil?
        Gitlab['gitlab_rails']['initial_root_password'] = SecretsHelper.generate_base64(32)
        Gitlab['gitlab_rails']['store_initial_root_password'] = true if Gitlab['gitlab_rails']['store_initial_root_password'].nil?
      end
90
91
92
93
94
95
96
97
98
99
100

      if Gitlab['gitlab_rails']['ci_jwt_signing_key']
        begin
          key = OpenSSL::PKey::RSA.new(Gitlab['gitlab_rails']['ci_jwt_signing_key'])
          raise 'ci_jwt_signing_key: The provided key is not private RSA key' unless key.private?
        rescue OpenSSL::PKey::RSAError
          raise 'ci_jwt_signing_key: The provided key is not valid RSA key'
        end
      else
        Gitlab['gitlab_rails']['ci_jwt_signing_key'] ||= SecretsHelper.generate_rsa(4096).to_pem
      end
101
    end
102
103
104
    # rubocop:enable Metrics/AbcSize
    # rubocop:enable Metrics/CyclomaticComplexity
    # rubocop:enable Metrics/PerceivedComplexity
105

106
107
108
109
110
    def parse_external_url
      return unless Gitlab['external_url']

      uri = URI(Gitlab['external_url'].to_s)

Ian Baum's avatar
Ian Baum committed
111
112
      raise "GitLab external URL must include a schema and FQDN, e.g. http://gitlab.example.com/" unless uri.host

113
114
      Gitlab['gitlab_rails']['gitlab_url'] = uri.to_s.chomp("/")

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
      Gitlab['user']['git_user_email'] ||= "gitlab@#{uri.host}"
      Gitlab['gitlab_rails']['gitlab_host'] = uri.host
      Gitlab['gitlab_rails']['gitlab_email_from'] ||= "gitlab@#{uri.host}"

      case uri.scheme
      when "http"
        Gitlab['gitlab_rails']['gitlab_https'] = false
        Nginx.parse_proxy_headers('nginx', false)
      when "https"
        Gitlab['gitlab_rails']['gitlab_https'] = true
        Gitlab['nginx']['ssl_certificate'] ||= "/etc/gitlab/ssl/#{uri.host}.crt"
        Gitlab['nginx']['ssl_certificate_key'] ||= "/etc/gitlab/ssl/#{uri.host}.key"
        Nginx.parse_proxy_headers('nginx', true)
      else
        raise "Unsupported external URL scheme: #{uri.scheme}"
      end

      unless ["", "/"].include?(uri.path)
        relative_url = uri.path.chomp("/")
        Gitlab['gitlab_rails']['gitlab_relative_url'] ||= relative_url
135
        Gitlab[WebServerHelper.service_name]['relative_url'] ||= relative_url
136
137
138
139
140
141
        Gitlab['gitlab_workhorse']['relative_url'] ||= relative_url
      end

      Gitlab['gitlab_rails']['gitlab_port'] = uri.port
    end

142
143
144
145
146
147
148
149
150
151
152
153
    def parse_database_adapter
      # TODO: Remove in GitLab 13

      adapter = Gitlab['gitlab_rails']['db_adapter']
      error_message = <<~MSG
          PostgreSQL is the only supported DBMS starting from GitLab 12.1 and you are using #{adapter}.
          Please refer https://docs.gitlab.com/omnibus/update/convert_to_omnibus.html#upgrading-from-non-omnibus-mysql-to-an-omnibus-installation-version-68
          to migrate to a PostgreSQL based installation.
      MSG
      raise error_message if adapter && adapter != 'postgresql'
    end

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    def parse_database_settings
      [
        [%w(gitlab_rails db_username), %w(postgresql sql_user)],
        [%w(gitlab_rails db_host), %w(postgresql listen_address)],
        [%w(gitlab_rails db_port), %w(postgresql port)],
      ].each do |left, right|
        next unless Gitlab[left.first][left.last].nil?

        better_value_from_gitlab_rb = Gitlab[right.first][right.last]
        default_from_attributes = Gitlab['node']['gitlab'][left.first.tr('_', '-')][left.last]
        Gitlab[left.first][left.last] = better_value_from_gitlab_rb || default_from_attributes
      end

      # Postgres allow multiple listen addresses, comma-separated values
      # In case of multi listen_address, will use the first address from list
      db_host = Gitlab['gitlab_rails']['db_host']
      if db_host&.include?(',')
        Gitlab['gitlab_rails']['db_host'] = db_host.split(',')[0]
        warning = [
          "Received gitlab_rails['db_host'] value was: #{db_host.to_json}.",
          "First listen_address '#{Gitlab['gitlab_rails']['db_host']}' will be used."
        ].join("\n  ")
        warn(warning)
      end

      # In case no other setting was provided for db_host, we use the socket
      # directory
      Gitlab['gitlab_rails']['db_host'] ||= Gitlab['postgresql']['dir'] || Gitlab['node']['postgresql']['dir']
    end

    def database_attributes
      Gitlab['node']['gitlab']['gitlab-rails'].keys.select { |k| k.start_with?('db_') }
    end

    def generate_main_database
      # If user hasn't specified a main database, for now, we will use the top
      # level `db_*` keys to populate one. In the future, when we are confident
      # in decomposition, we can deprecate top level `gitlab_rails['db_*']`
      # keys and ask users to explicitly set
      # `gitlab_rails['databases']['main']['db_*']` settings instead.
      Gitlab['gitlab_rails']['databases'] ||= {}
      Gitlab['gitlab_rails']['databases']['main'] ||= { 'enable' => true }

      # Set default value for attributes of main database based on top level
      # `gitlab_rails['db_*']` settings.
      database_attributes.each do |attribute|
200
201
202
203
        next unless Gitlab['gitlab_rails']['databases']['main'][attribute].nil?

        Gitlab['gitlab_rails']['databases']['main'][attribute] =
          [Gitlab['gitlab_rails'][attribute], Gitlab['node']['gitlab']['gitlab-rails'][attribute]].compact.first
204
205
206
207
208
209
210
211
      end
    end

    def parse_databases
      # TODO: Remove when we want to deprecate top level `gitlab_rails['db_*']`
      # settings
      generate_main_database

212
      # Weed out the databases that are either not allowed or not enabled explicitly (except for main and geo)
213
      Gitlab['gitlab_rails']['databases'].to_h.each do |database, settings|
214
        if !MAIN_DATABASES.include?(database) && settings['enable'] != true
215
216
217
218
          Gitlab['gitlab_rails']['databases'].delete(database)
          next
        end

219
        unless ALLOWED_DATABASES.include?(database)
220
221
222
223
224
          Gitlab['gitlab_rails']['databases'].delete(database)
          LoggingHelper.warning("Additional database `#{database}` not supported in Rails application. It will be ignored.")
        end
      end

225
      # Set default value of settings for other databases based on values used in `main` database.
226
      Gitlab['gitlab_rails']['databases'].each_key do |database|
227
        next if MAIN_DATABASES.include?(database)
228
229

        database_attributes.each do |attribute|
230
231
232
          next unless Gitlab['gitlab_rails']['databases'][database][attribute].nil?

          Gitlab['gitlab_rails']['databases'][database][attribute] = Gitlab['gitlab_rails']['databases']['main'][attribute]
233
        end
234
235
236
237
238

        # If additional database shares attributes with main
        # it should be skipped from database_tasks (running migrations)
        database_same_as_main = SHARED_DATABASE_ATTRIBUTES.all? { |attribute| Gitlab['gitlab_rails']['databases'][database][attribute] == Gitlab['gitlab_rails']['databases']['main'][attribute] }
        Gitlab['gitlab_rails']['databases'][database]['db_database_tasks'] = false if database_same_as_main
239
240
241
      end
    end

242
    def parse_runtime_dir
243
244
      if Gitlab['node']['filesystem'].nil?
        Chef::Log.warn 'No filesystem variables in Ohai, disabling runtime_dir'
Stan Hu's avatar
Stan Hu committed
245
        Gitlab['runtime_dir'] = nil
246
247
248
        return
      end

Stan Hu's avatar
Stan Hu committed
249
250
251
      return if Gitlab['runtime_dir']

      search_dirs = ['/run', '/dev/shm']
252
253

      search_dirs.each do |run_dir|
254
        fs = Gitlab['node']['filesystem']['by_mountpoint'][run_dir]
255
256
257
258

        if fs && fs['fs_type'] == 'tmpfs'
          Gitlab['runtime_dir'] = run_dir
          break
Stan Hu's avatar
Stan Hu committed
259
260
        end
      end
261
262
263
264

      Chef::Log.warn "Could not find a tmpfs in #{search_dirs}" if Gitlab['runtime_dir'].nil?

      Gitlab['runtime_dir']
265
266
    end

267
268
269
270
271
272
273
274
275
    def parse_shared_dir
      Gitlab['gitlab_rails']['shared_path'] ||= Gitlab['node']['gitlab']['gitlab-rails']['shared_path']
    end

    def parse_artifacts_dir
      # This requires the parse_shared_dir to be executed before
      Gitlab['gitlab_rails']['artifacts_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'artifacts')
    end

276
277
278
279
280
    def parse_external_diffs_dir
      # This requires the parse_shared_dir to be executed before
      Gitlab['gitlab_rails']['external_diffs_storage_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'external-diffs')
    end

281
282
283
284
285
    def parse_lfs_objects_dir
      # This requires the parse_shared_dir to be executed before
      Gitlab['gitlab_rails']['lfs_storage_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'lfs-objects')
    end

286
    def parse_uploads_dir
Micaël Bergeron's avatar
Micaël Bergeron committed
287
      Gitlab['gitlab_rails']['uploads_storage_path'] ||= public_path
288
289
    end

290
291
292
293
294
    def parse_packages_dir
      # This requires the parse_shared_dir to be executed before
      Gitlab['gitlab_rails']['packages_storage_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'packages')
    end

295
296
297
298
299
    def parse_dependency_proxy_dir
      # This requires the parse_shared_dir to be executed before
      Gitlab['gitlab_rails']['dependency_proxy_storage_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'dependency_proxy')
    end

300
301
302
303
304
    def parse_terraform_state_dir
      # This requires the parse_shared_dir to be executed before
      Gitlab['gitlab_rails']['terraform_state_storage_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'terraform_state')
    end

Darby Frey's avatar
Darby Frey committed
305
306
307
308
309
    def parse_ci_secure_files_dir
      # This requires the parse_shared_dir to be executed before
      Gitlab['gitlab_rails']['ci_secure_files_storage_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'ci_secure_files')
    end

310
311
312
313
314
    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')
    end

315
316
317
    def parse_pages_dir
      # This requires the parse_shared_dir to be executed before
      Gitlab['gitlab_rails']['pages_path'] ||= File.join(Gitlab['gitlab_rails']['shared_path'], 'pages')
318
      Gitlab['gitlab_rails']['pages_local_store_path'] ||= Gitlab['gitlab_rails']['pages_path']
319
320
    end

Marin Jankovski's avatar
Marin Jankovski committed
321
322
    def parse_repository_storage
      return if Gitlab['gitlab_rails']['repositories_storages']
Ian Baum's avatar
Ian Baum committed
323

Marin Jankovski's avatar
Marin Jankovski committed
324
325
326
327
328
      gitaly_address = Gitaly.gitaly_address

      Gitlab['gitlab_rails']['repositories_storages'] ||= {
        "default" => {
          "path" => "/var/opt/gitlab/git-data/repositories",
329
          "gitaly_address" => gitaly_address
Marin Jankovski's avatar
Marin Jankovski committed
330
331
332
333
        }
      }
    end

334
335
    def parse_gitlab_trusted_proxies
      Gitlab['nginx']['real_ip_trusted_addresses'] ||= Gitlab['node']['gitlab']['nginx']['real_ip_trusted_addresses']
336
      Gitlab['gitlab_rails']['trusted_proxies'] = Gitlab['nginx']['real_ip_trusted_addresses'] if Gitlab['gitlab_rails']['trusted_proxies'].nil?
337
338
    end

339
    def parse_incoming_email_logfile
340
      log_directory = Gitlab['mailroom']['log_directory'] || Gitlab[:node]['gitlab']['mailroom']['log_directory']
charlie ablett's avatar
charlie ablett committed
341
342
343
344
345
      return unless log_directory

      Gitlab['gitlab_rails']['incoming_email_log_file'] ||= File.join(log_directory, 'mail_room_json.log')
    end

346
    def parse_service_desk_email_logfile
347
      log_directory = Gitlab['mailroom']['log_directory'] || Gitlab[:node]['gitlab']['mailroom']['log_directory']
348
349
350
351
352
      return unless log_directory

      Gitlab['gitlab_rails']['service_desk_email_log_file'] ||= File.join(log_directory, 'mail_room_json.log')
    end

353
    def parse_maximum_request_duration
354
      Gitlab['gitlab_rails']['max_request_duration_seconds'] ||= (worker_timeout * 0.95).ceil
355

356
      return if Gitlab['gitlab_rails']['max_request_duration_seconds'] < worker_timeout
357
358
359
360

      raise "The maximum request duration needs to be smaller than the worker timeout (#{worker_timeout}s)"
    end

361
362
363
    def public_path
      "#{Gitlab['node']['package']['install-dir']}/embedded/service/gitlab-rails/public"
    end
364
365

    def worker_timeout
366
      service = WebServerHelper.service_name
367
368
      user_config = Gitlab[service]
      service_config = Gitlab['node']['gitlab'][service]
369
      (user_config['worker_timeout'] || service_config['worker_timeout']).to_i
370
    end
371
  end
372
end