Welcome to mirror list, hosted at ThFree Co, Russian Federation.

secrets.rb « doctor « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: cd075569d105e3f4f63c4a1f53b5cace096ac822 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# frozen_string_literal: true

module Gitlab
  module Doctor
    class Secrets
      attr_reader :logger

      def initialize(logger)
        @logger = logger
      end

      def run!
        logger.info "Checking encrypted values in the database"
        Rails.application.eager_load! unless Rails.application.config.eager_load

        models_with_attributes = Hash.new { |h, k| h[k] = [] }

        models_with_encrypted_attributes.each do |model|
          models_with_attributes[model] += model.encrypted_attributes.keys
        end

        models_with_encrypted_tokens.each do |model|
          models_with_attributes[model] += model.encrypted_token_authenticatable_fields
        end

        check_model_attributes(models_with_attributes)

        logger.info "Done!"
      end

      private

      # Skipping initializers may be needed if those attempt to access
      # encrypted data on initialization and could fail because of it.
      #
      # format example:
      # {
      #   model_class => {
      #     [
      #       { action: :create, filters: [:before, :filter_name1] },
      #       { action: :update, filters: [:after,  :filter_name2] }
      #     ]
      #   }
      # }
      MODEL_INITIALIZERS_TO_SKIP = {
        Integration => [
          { action: :initialize, filters: [:after, :initialize_properties] }
        ]
      }.freeze

      def check_model_attributes(models_with_attributes)
        running_failures = 0

        models_with_attributes.each do |model, attributes|
          failures_per_row = Hash.new { |h, k| h[k] = [] }

          with_skipped_callbacks_for(model) do
            model.find_each do |data|
              attributes.each do |att|
                failures_per_row[data.id] << att unless valid_attribute?(data, att)
              end
            end
          end

          running_failures += failures_per_row.keys.count
          output_failures_for_model(model, failures_per_row)
        end

        logger.info "Total: #{running_failures} row(s) affected".color(:blue)
      end

      def output_failures_for_model(model, failures)
        status_color = failures.empty? ? :green : :red

        logger.info "- #{model} failures: #{failures.count}".color(status_color)
        failures.each do |row_id, attributes|
          logger.debug "  - #{model}[#{row_id}]: #{attributes.join(", ")}".color(:red)
        end
      end

      def models_with_encrypted_attributes
        all_models.select { |d| d.encrypted_attributes.present? }
      end

      def models_with_encrypted_tokens
        all_models.select do |d|
          d.include?(TokenAuthenticatable) && d.encrypted_token_authenticatable_fields.present?
        end
      end

      def all_models
        @all_models ||= ApplicationRecord.descendants
      end

      def valid_attribute?(data, attr)
        data.send(attr) # rubocop:disable GitlabSecurity/PublicSend

        true
      rescue OpenSSL::Cipher::CipherError, TypeError
        false
      rescue StandardError => e
        logger.debug "> Something went wrong for #{data.class.name}[#{data.id}].#{attr}: #{e}".color(:red)

        false
      end

      # WARNING: using this logic in other places than a Rake task will need a
      # different approach, as simply setting the callback again is not thread-safe
      def with_skipped_callbacks_for(model)
        raise StandardError, 'can only be used in a Rake environment' unless Gitlab::Runtime.rake?

        skip_callbacks_for_model(model)

        yield

        skip_callbacks_for_model(model, reset: true)
      end

      def skip_callbacks_for_model(model, reset: false)
        MODEL_INITIALIZERS_TO_SKIP.each do |klass, initializers|
          next unless model <= klass

          initializers.each do |initializer|
            if reset
              model.set_callback(initializer[:action], *initializer[:filters])
            else
              model.skip_callback(initializer[:action], *initializer[:filters])
            end
          end
        end
      end
    end
  end
end