Commit d4c57984 authored by Alan Paruszewski's avatar Alan Paruszewski

Remove metrics for Web Application Firewall

This change removes metrics for WAF (Ingress Modsecurity) metrics.

Changelog: removed
parent 631c7c83
---
name: ingress_modsecurity
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20194
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258554
milestone: '12.5'
type: development
group: group::container security
default_enabled: false
......@@ -676,9 +676,6 @@ Gitlab.ee do
Settings.cron_jobs['sync_seat_link_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['sync_seat_link_worker']['cron'] ||= "#{rand(60)} 3 * * * UTC"
Settings.cron_jobs['sync_seat_link_worker']['job_class'] = 'SyncSeatLinkWorker'
Settings.cron_jobs['web_application_firewall_metrics_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['web_application_firewall_metrics_worker']['cron'] ||= '0 1 * * 0'
Settings.cron_jobs['web_application_firewall_metrics_worker']['job_class'] = 'IngressModsecurityCounterMetricsWorker'
Settings.cron_jobs['users_create_statistics_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['users_create_statistics_worker']['cron'] ||= '2 15 * * *'
Settings.cron_jobs['users_create_statistics_worker']['job_class'] = 'Users::CreateStatisticsWorker'
......
......@@ -7,7 +7,8 @@ product_stage: protect
product_group: group::container security
product_category: web_firewall
value_type: number
status: deprecated
status: removed
milestone_removed: 14.0
time_frame: all
data_source: database
distribution:
......
......@@ -7,7 +7,8 @@ product_stage: protect
product_group: group::container security
product_category: web_firewall
value_type: number
status: deprecated
status: removed
milestone_removed: 14.0
time_frame: all
data_source: database
distribution:
......
......@@ -6,7 +6,8 @@ product_stage: protect
product_group: group::container security
product_category: web_firewall
value_type: number
status: deprecated
status: removed
milestone_removed: 14.0
time_frame: all
data_source: database
distribution:
......
......@@ -6,7 +6,8 @@ product_stage: protect
product_group: group::container security
product_category: web_firewall
value_type: number
status: deprecated
status: removed
milestone_removed: 14.0
time_frame: all
data_source: database
distribution:
......
......@@ -6,7 +6,8 @@ product_stage: protect
product_group: group::container security
product_category: web_firewall
value_type: number
status: deprecated
status: removed
milestone_removed: 14.0
time_frame: all
data_source: database
distribution:
......
......@@ -6,7 +6,8 @@ product_stage: protect
product_group: group::container security
product_category: web_firewall
value_type: number
status: deprecated
status: removed
milestone_removed: 14.0
time_frame: all
data_source: database
distribution:
......
......@@ -6,7 +6,8 @@ product_stage: protect
product_group: group::container security
product_category: web_firewall
value_type: boolean
status: deprecated
status: removed
milestone_removed: 14.0
time_frame: none
data_source: system
distribution:
......
......@@ -7134,7 +7134,7 @@ Whether or not ModSecurity is enabled within Ingress
Group: `group::container security`
Status: `deprecated`
Status: `removed`
Tiers: `free`, `premium`, `ultimate`
......
......@@ -1354,7 +1354,6 @@ The following is example content of the Usage Ping payload.
"reply_by_email_enabled": "incoming+%{key}@incoming.gitlab.com",
"signup_enabled": true,
"web_ide_clientside_preview_enabled": true,
"ingress_modsecurity_enabled": true,
"projects_with_expiration_policy_disabled": 999,
"projects_with_expiration_policy_enabled": 999,
...
......
# frozen_string_literal: true
module EE
module Security
##
# This service measures usage of the Modsecurity Web Application Firewall across the entire
# instance's deployed environments.
#
##
class IngressModsecurityUsageService
BATCH_SIZE = 1
def initialize
@statistics_unavailable_count = 0
@packets_processed_count = 0
@packets_anomalous_count = 0
end
# rubocop: disable CodeReuse/ActiveRecord
def execute
clusters_with_enabled_modsecurity.find_each(batch_size: BATCH_SIZE) do |cluster|
cluster.environments.each do |environment|
result = anomaly_results_for_cluster_and_environment(cluster, environment)
if result.nil?
@statistics_unavailable_count += 1
else
@packets_processed_count += result[:total_traffic]
@packets_anomalous_count += result[:total_anomalous_traffic]
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
{
statistics_unavailable: @statistics_unavailable_count.to_i,
packets_processed: @packets_processed_count.to_i,
packets_anomalous: @packets_anomalous_count.to_i
}
end
private
def anomaly_results_for_cluster_and_environment(cluster, environment)
# As defined in config/initializers/1_settings.rb#562, IngressModsecurityCounterMetricsWorker will be executed
# once a week. That is why when we are collecting data from clusters we are querying for the last 7 days.
::Security::WafAnomalySummaryService
.new(environment: environment, cluster: cluster, from: 7.days.ago.iso8601, options: { timeout: 10 })
.execute(totals_only: true)
rescue StandardError => e
::Gitlab::ErrorTracking.track_exception(e, environment_id: environment&.id, cluster_id: cluster&.id)
nil
end
def clusters_with_enabled_modsecurity
::Clusters::Cluster
.with_enabled_modsecurity
.with_available_elasticstack
.distinct_with_deployed_environments
.preload_elasticstack
.preload_environments
end
end
end
end
......@@ -314,15 +314,6 @@
:idempotent: true
:tags:
- :exclude_from_kubernetes
- :name: cronjob:ingress_modsecurity_counter_metrics
:worker_name: IngressModsecurityCounterMetricsWorker
:feature_category: :web_firewall
:has_external_dependencies: true
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:tags: []
- :name: cronjob:iterations_update_status
:worker_name: IterationsUpdateStatusWorker
:feature_category: :issue_tracking
......
# frozen_string_literal: true
class IngressModsecurityCounterMetricsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
sidekiq_options retry: 3
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
include ExclusiveLeaseGuard
feature_category :web_firewall
worker_has_external_dependencies!
LEASE_TIMEOUT = 1.hour
def perform
return unless Feature.enabled?(:usage_ingress_modsecurity_counter, default_enabled: true)
try_obtain_lease do
cluster_app_metrics = EE::Security::IngressModsecurityUsageService.new.execute
Gitlab::UsageDataCounters::IngressModsecurityCounter.add(
cluster_app_metrics[:statistics_unavailable],
cluster_app_metrics[:packets_processed],
cluster_app_metrics[:packets_anomalous]
)
end
end
private
def lease_timeout
LEASE_TIMEOUT
end
def lease_release?
false
end
end
---
name: usage_ingress_modsecurity_counter
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28535
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/32358
milestone: '13.1'
type: development
group: group::container security
default_enabled: true
......@@ -6,7 +6,8 @@ product_stage: protect
product_group: group::container security
product_category: web_firewall
value_type: number
status: deprecated
status: removed
milestone_removed: 14.0
time_frame: all
data_source: database
distribution:
......
......@@ -52,7 +52,6 @@ module EE
def usage_data_counters
super + [
::Gitlab::UsageDataCounters::LicensesList,
::Gitlab::UsageDataCounters::IngressModsecurityCounter,
::Gitlab::StatusPage::UsageDataCounters::IncidentCounter,
::Gitlab::UsageDataCounters::NetworkPolicyCounter
]
......
# frozen_string_literal: true
module Gitlab::UsageDataCounters
class IngressModsecurityCounter < BaseCounter
KNOWN_EVENTS = %w[statistics_unavailable packets_processed packets_anomalous].freeze
PREFIX = 'ingress_modsecurity'
class << self
def add(statistics_unavailable, packets_processed, packets_anomalous)
return unless Gitlab::CurrentSettings.usage_ping_enabled?
Gitlab::Redis::SharedState.with do |redis|
redis.multi do
redis.set(redis_key(:statistics_unavailable), statistics_unavailable)
redis.incrby(redis_key(:packets_processed), packets_processed)
redis.incrby(redis_key(:packets_anomalous), packets_anomalous)
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::IngressModsecurityCounter, :clean_gitlab_redis_shared_state do
describe '.add' do
it 'increases packets_processed and packets_anomalous counters and sets statistics_unavailable counter' do
described_class.add(3, 10_200, 2_500)
expect(described_class.totals).to eq(
ingress_modsecurity_packets_anomalous: 2_500,
ingress_modsecurity_packets_processed: 10_200,
ingress_modsecurity_statistics_unavailable: 3
)
described_class.add(2, 800, 500)
expect(described_class.totals).to eq(
ingress_modsecurity_packets_anomalous: 3_000,
ingress_modsecurity_packets_processed: 11_000,
ingress_modsecurity_statistics_unavailable: 2
)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::Security::IngressModsecurityUsageService do
describe '#execute' do
let(:environment) { create(:environment) }
let(:ingress_mode) { :modsecurity_blocking }
let(:deployments) { [] }
let!(:cluster) { create(:cluster, deployments: deployments) }
let!(:ingress) { create(:clusters_applications_ingress, ingress_mode, cluster: cluster) }
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, :installed, cluster: cluster) }
subject { described_class.new.execute }
before do
allow_any_instance_of(::Security::WafAnomalySummaryService).to receive(:execute)
end
context 'when cluster is disabled' do
let(:cluster) { create(:cluster, :disabled, deployments: deployments) }
it 'gathers ingress data' do
expect(subject[:statistics_unavailable]).to eq(0)
expect(subject[:packets_processed]).to eq(0)
expect(subject[:packets_anomalous]).to eq(0)
end
end
context 'when environment is not available' do
let(:environment) { create(:environment, state: :stopped) }
it 'gathers ingress data' do
expect(subject[:statistics_unavailable]).to eq(0)
expect(subject[:packets_processed]).to eq(0)
expect(subject[:packets_anomalous]).to eq(0)
end
end
context 'when environment is available' do
context 'when deployment is unsuccessful' do
let(:deployments) { [deployment] }
let!(:deployment) { create(:deployment, :failed, environment: environment) }
it 'gathers ingress data' do
expect(subject[:statistics_unavailable]).to eq(0)
expect(subject[:packets_processed]).to eq(0)
expect(subject[:packets_anomalous]).to eq(0)
end
end
context 'when deployment is successful' do
let(:deployments) { [deployment] }
let!(:deployment) { create(:deployment, :success, environment: environment) }
let(:waf_anomaly_summary) { { total_traffic: 1000, total_anomalous_traffic: 200 } }
before do
allow_any_instance_of(::Security::WafAnomalySummaryService).to receive(:execute).and_return(waf_anomaly_summary)
allow(::Gitlab::ErrorTracking).to receive(:track_exception)
end
context 'when modsecurity statistics are available' do
it 'gathers ingress data' do
expect(subject[:statistics_unavailable]).to eq(0)
expect(subject[:packets_processed]).to eq(1000)
expect(subject[:packets_anomalous]).to eq(200)
end
end
context 'when modsecurity statistics are not available' do
let(:waf_anomaly_summary) { nil }
it 'gathers ingress data' do
expect(subject[:statistics_unavailable]).to eq(1)
expect(subject[:packets_processed]).to eq(0)
expect(subject[:packets_anomalous]).to eq(0)
end
end
context 'when modsecurity statistics process is raising exception' do
before do
allow_any_instance_of(::Security::WafAnomalySummaryService).to receive(:execute).and_raise(StandardError)
end
it 'gathers ingress data' do
expect(subject[:statistics_unavailable]).to eq(1)
expect(subject[:packets_processed]).to eq(0)
expect(subject[:packets_anomalous]).to eq(0)
end
it 'tracks exception' do
expect(::Gitlab::ErrorTracking).to receive(:track_exception).with(StandardError, environment_id: environment.id, cluster_id: cluster.id)
subject
end
end
context 'with multiple environments' do
let!(:environment_2) { create(:environment) }
let!(:cluster_2) { create(:cluster, deployments: [deployment_2]) }
let!(:deployment_2) { create(:deployment, :success, environment: environment_2) }
let!(:ingress_2) { create(:clusters_applications_ingress, ingress_mode, cluster: cluster_2) }
let!(:elastic_stack_2) { create(:clusters_applications_elastic_stack, :installed, cluster: cluster_2) }
it 'gathers ingress data from multiple environments' do
expect(subject[:statistics_unavailable]).to eq(0)
expect(subject[:packets_processed]).to eq(2000)
expect(subject[:packets_anomalous]).to eq(400)
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IngressModsecurityCounterMetricsWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
subject(:worker) { described_class.new }
let(:ingress_usage_service) { instance_double('EE::Security::IngressModsecurityUsageService', execute: usage_statistics) }
let(:usage_statistics) do
{
statistics_unavailable: 2,
packets_processed: 10_200,
packets_anomalous: 2_500
}
end
before do
allow(EE::Security::IngressModsecurityUsageService).to receive(:new) { ingress_usage_service }
end
describe '#perform' do
context 'when feature flag is disabled' do
before do
stub_feature_flags(usage_ingress_modsecurity_counter: false)
end
it 'does not update the usae counter' do
worker.perform
expect(Gitlab::UsageDataCounters::IngressModsecurityCounter.totals).to eq(
ingress_modsecurity_packets_anomalous: 0,
ingress_modsecurity_packets_processed: 0,
ingress_modsecurity_statistics_unavailable: 0
)
end
end
context 'with exclusive lease' do
let(:lease_key) { "#{described_class.name.underscore}" }
before do
stub_exclusive_lease_taken(lease_key)
end
it 'does not allow to add counters concurrently' do
expect(Gitlab::UsageDataCounters::IngressModsecurityCounter).not_to receive(:add)
worker.perform
end
end
it 'updates usage counter' do
worker.perform
expect(Gitlab::UsageDataCounters::IngressModsecurityCounter.totals).to eq(
ingress_modsecurity_packets_anomalous: 2_500,
ingress_modsecurity_packets_processed: 10_200,
ingress_modsecurity_statistics_unavailable: 2
)
end
end
end
......@@ -189,7 +189,6 @@ module Gitlab
services_usage,
usage_counters,
user_preferences_usage,
ingress_modsecurity_usage,
container_expiration_policies_usage,
service_desk_counts,
email_campaign_counts
......@@ -296,7 +295,6 @@ module Gitlab
reply_by_email_enabled: alt_usage_data(fallback: nil) { Gitlab::IncomingEmail.enabled? },
signup_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.allow_signup? },
web_ide_clientside_preview_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.web_ide_clientside_preview_enabled? },
ingress_modsecurity_enabled: Feature.enabled?(:ingress_modsecurity),
grafana_link_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.grafana_enabled? },
gitpod_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.gitpod_enabled? }
}
......@@ -378,29 +376,6 @@ module Gitlab
Gitlab::UsageData::Topology.new.topology_usage_data
end
# rubocop: disable UsageData/DistinctCountByLargeForeignKey
def ingress_modsecurity_usage
##
# This method measures usage of the Modsecurity Web Application Firewall across the entire
# instance's deployed environments.
#
# NOTE: this service is an approximation as it does not yet take into account if environment
# is enabled and only measures applications installed using GitLab Managed Apps (disregards
# CI-based managed apps).
#
# More details: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28331#note_318621786
##
column = ::Deployment.arel_table[:environment_id]
{
ingress_modsecurity_logging: distinct_count(successful_deployments_with_cluster(::Clusters::Applications::Ingress.modsecurity_enabled.logging), column),
ingress_modsecurity_blocking: distinct_count(successful_deployments_with_cluster(::Clusters::Applications::Ingress.modsecurity_enabled.blocking), column),
ingress_modsecurity_disabled: distinct_count(successful_deployments_with_cluster(::Clusters::Applications::Ingress.modsecurity_disabled), column),
ingress_modsecurity_not_installed: distinct_count(successful_deployments_with_cluster(::Clusters::Applications::Ingress.modsecurity_not_installed), column)
}
end
# rubocop: enable UsageData/DistinctCountByLargeForeignKey
# rubocop: disable CodeReuse/ActiveRecord
def container_expiration_policies_usage
results = {}
......
......@@ -33,24 +33,6 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
end
context 'joined relations' do
context 'counted attribute comes from joined relation' do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with:
# distinct_count(
# ::Clusters::Applications::Ingress.modsecurity_enabled.logging
# .joins(cluster: :deployments)
# .merge(::Clusters::Cluster.enabled)
# .merge(Deployment.success),
# ::Deployment.arel_table[:environment_id]
# )
let(:key_path) { 'counts.ingress_modsecurity_logging' }
let(:name_suggestion) do
constrains = /'\(clusters_applications_ingress\.modsecurity_enabled = TRUE AND clusters_applications_ingress\.modsecurity_mode = \d+ AND clusters.enabled = TRUE AND deployments.status = \d+\)'/
/count_distinct_environment_id_from_<adjective describing\: #{constrains}>_deployments_<with>_<adjective describing\: #{constrains}>_clusters_<having>_<adjective describing\: #{constrains}>_clusters_applications_ingress/
end
end
end
context 'counted attribute comes from source relation' do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
......
......@@ -967,138 +967,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
describe '.ingress_modsecurity_usage' do
subject { described_class.ingress_modsecurity_usage }
let(:environment) { create(:environment) }
let(:project) { environment.project }
let(:environment_scope) { '*' }
let(:deployment) { create(:deployment, :success, environment: environment, project: project, cluster: cluster) }
let(:cluster) { create(:cluster, environment_scope: environment_scope, projects: [project]) }
let(:ingress_mode) { :modsecurity_blocking }
let!(:ingress) { create(:clusters_applications_ingress, ingress_mode, cluster: cluster) }
context 'when cluster is disabled' do
let(:cluster) { create(:cluster, :disabled, projects: [project]) }
it 'gathers ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(0)
expect(subject[:ingress_modsecurity_blocking]).to eq(0)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
context 'when deployment is unsuccessful' do
let!(:deployment) { create(:deployment, :failed, environment: environment, project: project, cluster: cluster) }
it 'gathers ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(0)
expect(subject[:ingress_modsecurity_blocking]).to eq(0)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
context 'when deployment is successful' do
let!(:deployment) { create(:deployment, :success, environment: environment, project: project, cluster: cluster) }
context 'when modsecurity is in blocking mode' do
it 'gathers ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(0)
expect(subject[:ingress_modsecurity_blocking]).to eq(1)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
context 'when modsecurity is in logging mode' do
let(:ingress_mode) { :modsecurity_logging }
it 'gathers ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(1)
expect(subject[:ingress_modsecurity_blocking]).to eq(0)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
context 'when modsecurity is disabled' do
let(:ingress_mode) { :modsecurity_disabled }
it 'gathers ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(0)
expect(subject[:ingress_modsecurity_blocking]).to eq(0)
expect(subject[:ingress_modsecurity_disabled]).to eq(1)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
context 'when modsecurity is not installed' do
let(:ingress_mode) { :modsecurity_not_installed }
it 'gathers ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(0)
expect(subject[:ingress_modsecurity_blocking]).to eq(0)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(1)
end
end
context 'with multiple projects' do
let(:environment_2) { create(:environment) }
let(:project_2) { environment_2.project }
let(:cluster_2) { create(:cluster, environment_scope: environment_scope, projects: [project_2]) }
let!(:ingress_2) { create(:clusters_applications_ingress, :modsecurity_logging, cluster: cluster_2) }
let!(:deployment_2) { create(:deployment, :success, environment: environment_2, project: project_2, cluster: cluster_2) }
it 'gathers non-duplicated ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(1)
expect(subject[:ingress_modsecurity_blocking]).to eq(1)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
context 'with multiple deployments' do
let!(:deployment_2) { create(:deployment, :success, environment: environment, project: project, cluster: cluster) }
it 'gathers non-duplicated ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(0)
expect(subject[:ingress_modsecurity_blocking]).to eq(1)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
context 'with multiple projects' do
let(:environment_2) { create(:environment) }
let(:project_2) { environment_2.project }
let!(:deployment_2) { create(:deployment, :success, environment: environment_2, project: project_2, cluster: cluster) }
let(:cluster) { create(:cluster, environment_scope: environment_scope, projects: [project, project_2]) }
it 'gathers ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(0)
expect(subject[:ingress_modsecurity_blocking]).to eq(2)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
context 'with multiple environments' do
let!(:environment_2) { create(:environment, project: project) }
let!(:deployment_2) { create(:deployment, :success, environment: environment_2, project: project, cluster: cluster) }
it 'gathers ingress data' do
expect(subject[:ingress_modsecurity_logging]).to eq(0)
expect(subject[:ingress_modsecurity_blocking]).to eq(2)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
expect(subject[:ingress_modsecurity_not_installed]).to eq(0)
end
end
end
end
describe '.grafana_embed_usage_data' do
subject { described_class.grafana_embed_usage_data }
......
......@@ -163,7 +163,6 @@ module UsageDataHelpers
database
prometheus_metrics_enabled
web_ide_clientside_preview_enabled
ingress_modsecurity_enabled
object_store
topology
).freeze
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment