summaryrefslogtreecommitdiff
path: root/app/services/clusters/applications/ingress_modsecurity_usage_service.rb
blob: 4aac8bb3cbda399d2ac3def1b072a2a44e3cc50e (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
# frozen_string_literal: true

# rubocop: disable CodeReuse/ActiveRecord
module Clusters
  module Applications
    ##
    # This service measures usage of the Modsecurity Web Application Firewall across the entire
    # instance's deployed environments.
    #
    # The default configuration is`AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE=DetectionOnly` so we
    # measure non-default values via definition of either ci_variables or ci_pipeline_variables.
    # Since both these values are encrypted, we must decrypt and count them in memory.
    #
    # NOTE: this service is an approximation as it does not yet take into account `environment_scope` or `ci_group_variables`.
    ##
    class IngressModsecurityUsageService
      ADO_MODSEC_KEY = "AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE"

      def initialize(blocking_count: 0, disabled_count: 0)
        @blocking_count = blocking_count
        @disabled_count = disabled_count
      end

      def execute
        conditions = -> { merge(::Environment.available).merge(::Deployment.success).where(key: ADO_MODSEC_KEY) }

        ci_pipeline_var_enabled =
          ::Ci::PipelineVariable
            .joins(pipeline: { environments: :last_visible_deployment })
            .merge(conditions)
            .order('deployments.environment_id, deployments.id DESC')

        ci_var_enabled =
          ::Ci::Variable
            .joins(project: { environments: :last_visible_deployment })
            .merge(conditions)
            .merge(
              # Give priority to pipeline variables by excluding from dataset
              ::Ci::Variable.joins(project: :environments).where.not(
                environments: { id: ci_pipeline_var_enabled.select('DISTINCT ON (deployments.environment_id) deployments.environment_id') }
              )
            ).select('DISTINCT ON (deployments.environment_id) ci_variables.*')

        sum_modsec_config_counts(
          ci_pipeline_var_enabled.select('DISTINCT ON (deployments.environment_id) ci_pipeline_variables.*')
        )
        sum_modsec_config_counts(ci_var_enabled)

        {
          ingress_modsecurity_blocking: @blocking_count,
          ingress_modsecurity_disabled: @disabled_count
        }
      end

      private

      # These are encrypted so we must decrypt and count in memory
      def sum_modsec_config_counts(dataset)
        dataset.each do |var|
          case var.value
          when "On" then @blocking_count += 1
          when "Off" then @disabled_count += 1
            # `else` could be default or any unsupported user input
          end
        end
      end
    end
  end
end