Commit 4ececb6d authored by Lucas Charles's avatar Lucas Charles Committed by Achilleas Pipinellis

Add modsecurity_enabled toggle to Ingress managed app

This replaces the previous behavior hid behind the `ingress_modsecurity`
feature flag, instead requiring "opt-in" to enable modsecurity during
ingress installation

See https://gitlab.com/gitlab-org/gitlab/issues/39140 for details
parent 554cce8b
...@@ -53,6 +53,7 @@ export default class Clusters { ...@@ -53,6 +53,7 @@ export default class Clusters {
helpPath, helpPath,
ingressHelpPath, ingressHelpPath,
ingressDnsHelpPath, ingressDnsHelpPath,
ingressModSecurityHelpPath,
environmentsHelpPath, environmentsHelpPath,
clustersHelpPath, clustersHelpPath,
deployBoardsHelpPath, deployBoardsHelpPath,
...@@ -69,6 +70,7 @@ export default class Clusters { ...@@ -69,6 +70,7 @@ export default class Clusters {
helpPath, helpPath,
ingressHelpPath, ingressHelpPath,
ingressDnsHelpPath, ingressDnsHelpPath,
ingressModSecurityHelpPath,
environmentsHelpPath, environmentsHelpPath,
clustersHelpPath, clustersHelpPath,
deployBoardsHelpPath, deployBoardsHelpPath,
...@@ -169,6 +171,7 @@ export default class Clusters { ...@@ -169,6 +171,7 @@ export default class Clusters {
ingressHelpPath: this.state.ingressHelpPath, ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath, managePrometheusPath: this.state.managePrometheusPath,
ingressDnsHelpPath: this.state.ingressDnsHelpPath, ingressDnsHelpPath: this.state.ingressDnsHelpPath,
ingressModSecurityHelpPath: this.state.ingressModSecurityHelpPath,
cloudRunHelpPath: this.state.cloudRunHelpPath, cloudRunHelpPath: this.state.cloudRunHelpPath,
providerType: this.state.providerType, providerType: this.state.providerType,
preInstalledKnative: this.state.preInstalledKnative, preInstalledKnative: this.state.preInstalledKnative,
......
...@@ -56,6 +56,11 @@ export default { ...@@ -56,6 +56,11 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
ingressModSecurityHelpPath: {
type: String,
required: false,
default: '',
},
cloudRunHelpPath: { cloudRunHelpPath: {
type: String, type: String,
required: false, required: false,
...@@ -112,6 +117,9 @@ export default { ...@@ -112,6 +117,9 @@ export default {
ingressInstalled() { ingressInstalled() {
return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED; return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED;
}, },
ingressEnableModsecurity() {
return this.applications.ingress.modsecurity_enabled;
},
ingressExternalEndpoint() { ingressExternalEndpoint() {
return this.applications.ingress.externalIp || this.applications.ingress.externalHostname; return this.applications.ingress.externalIp || this.applications.ingress.externalHostname;
}, },
...@@ -127,6 +135,18 @@ export default { ...@@ -127,6 +135,18 @@ export default {
enableClusterApplicationElasticStack() { enableClusterApplicationElasticStack() {
return gon.features && gon.features.enableClusterApplicationElasticStack; return gon.features && gon.features.enableClusterApplicationElasticStack;
}, },
ingressModSecurityDescription() {
const escapedUrl = _.escape(this.ingressModSecurityHelpPath);
return sprintf(
s__('ClusterIntegration|Learn more about %{startLink}ModSecurity%{endLink}'),
{
startLink: `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer">`,
endLink: '</a>',
},
false,
);
},
ingressDescription() { ingressDescription() {
return sprintf( return sprintf(
_.escape( _.escape(
...@@ -135,9 +155,9 @@ export default { ...@@ -135,9 +155,9 @@ export default {
), ),
), ),
{ {
pricingLink: `<strong><a href="https://cloud.google.com/compute/pricing#lb" pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb"
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|pricing'))}</a></strong>`, ${_.escape(s__('ClusterIntegration|pricing'))}</a>`,
}, },
false, false,
); );
...@@ -311,6 +331,9 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity ...@@ -311,6 +331,9 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
:request-reason="applications.ingress.requestReason" :request-reason="applications.ingress.requestReason"
:installed="applications.ingress.installed" :installed="applications.ingress.installed"
:install-failed="applications.ingress.installFailed" :install-failed="applications.ingress.installFailed"
:install-application-request-params="{
modsecurity_enabled: applications.ingress.modsecurity_enabled,
}"
:uninstallable="applications.ingress.uninstallable" :uninstallable="applications.ingress.uninstallable"
:uninstall-successful="applications.ingress.uninstallSuccessful" :uninstall-successful="applications.ingress.uninstallSuccessful"
:uninstall-failed="applications.ingress.uninstallFailed" :uninstall-failed="applications.ingress.uninstallFailed"
...@@ -326,6 +349,26 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity ...@@ -326,6 +349,26 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
}} }}
</p> </p>
<template>
<div class="form-group">
<div class="form-check form-check-inline">
<input
v-model="applications.ingress.modsecurity_enabled"
:disabled="ingressInstalled"
type="checkbox"
autocomplete="off"
class="form-check-input"
/>
<label class="form-check-label label-bold" for="ingress-enable-modsecurity">
{{ s__('ClusterIntegration|Enable Web Application Firewall') }}
</label>
</div>
<p class="form-text text-muted">
<strong v-html="ingressModSecurityDescription"></strong>
</p>
</div>
</template>
<template v-if="ingressInstalled"> <template v-if="ingressInstalled">
<div class="form-group"> <div class="form-group">
<label for="ingress-endpoint">{{ s__('ClusterIntegration|Ingress Endpoint') }}</label> <label for="ingress-endpoint">{{ s__('ClusterIntegration|Ingress Endpoint') }}</label>
...@@ -375,7 +418,9 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity ...@@ -375,7 +418,9 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
</p> </p>
</template> </template>
<template v-if="!ingressInstalled"> <template v-if="!ingressInstalled">
<div class="bs-callout bs-callout-info" v-html="ingressDescription"></div> <div class="bs-callout bs-callout-info">
<strong v-html="ingressDescription"></strong>
</div>
</template> </template>
</div> </div>
</application-row> </application-row>
......
...@@ -52,6 +52,7 @@ export default class ClusterStore { ...@@ -52,6 +52,7 @@ export default class ClusterStore {
ingress: { ingress: {
...applicationInitialState, ...applicationInitialState,
title: s__('ClusterIntegration|Ingress'), title: s__('ClusterIntegration|Ingress'),
modsecurity_enabled: false,
externalIp: null, externalIp: null,
externalHostname: null, externalHostname: null,
}, },
...@@ -108,6 +109,7 @@ export default class ClusterStore { ...@@ -108,6 +109,7 @@ export default class ClusterStore {
helpPath, helpPath,
ingressHelpPath, ingressHelpPath,
ingressDnsHelpPath, ingressDnsHelpPath,
ingressModSecurityHelpPath,
environmentsHelpPath, environmentsHelpPath,
clustersHelpPath, clustersHelpPath,
deployBoardsHelpPath, deployBoardsHelpPath,
...@@ -116,6 +118,7 @@ export default class ClusterStore { ...@@ -116,6 +118,7 @@ export default class ClusterStore {
this.state.helpPath = helpPath; this.state.helpPath = helpPath;
this.state.ingressHelpPath = ingressHelpPath; this.state.ingressHelpPath = ingressHelpPath;
this.state.ingressDnsHelpPath = ingressDnsHelpPath; this.state.ingressDnsHelpPath = ingressDnsHelpPath;
this.state.ingressModSecurityHelpPath = ingressModSecurityHelpPath;
this.state.environmentsHelpPath = environmentsHelpPath; this.state.environmentsHelpPath = environmentsHelpPath;
this.state.clustersHelpPath = clustersHelpPath; this.state.clustersHelpPath = clustersHelpPath;
this.state.deployBoardsHelpPath = deployBoardsHelpPath; this.state.deployBoardsHelpPath = deployBoardsHelpPath;
...@@ -207,6 +210,8 @@ export default class ClusterStore { ...@@ -207,6 +210,8 @@ export default class ClusterStore {
if (appId === INGRESS) { if (appId === INGRESS) {
this.state.applications.ingress.externalIp = serverAppEntry.external_ip; this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
this.state.applications.ingress.externalHostname = serverAppEntry.external_hostname; this.state.applications.ingress.externalHostname = serverAppEntry.external_hostname;
this.state.applications.ingress.modsecurity_enabled =
serverAppEntry.modsecurity_enabled || this.state.applications.ingress.modsecurity_enabled;
} else if (appId === CERT_MANAGER) { } else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email = this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email; this.state.applications.cert_manager.email || serverAppEntry.email;
......
...@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController ...@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
end end
def cluster_application_params def cluster_application_params
params.permit(:application, :hostname, :kibana_hostname, :email, :stack) params.permit(:application, :hostname, :kibana_hostname, :email, :stack, :modsecurity_enabled)
end end
def cluster_application_destroy_params def cluster_application_destroy_params
......
...@@ -14,6 +14,7 @@ module Clusters ...@@ -14,6 +14,7 @@ module Clusters
include AfterCommitQueue include AfterCommitQueue
default_value_for :ingress_type, :nginx default_value_for :ingress_type, :nginx
default_value_for :modsecurity_enabled, false
default_value_for :version, VERSION default_value_for :version, VERSION
enum ingress_type: { enum ingress_type: {
...@@ -73,7 +74,7 @@ module Clusters ...@@ -73,7 +74,7 @@ module Clusters
private private
def specification def specification
return {} unless Feature.enabled?(:ingress_modsecurity) return {} unless modsecurity_enabled
{ {
"controller" => { "controller" => {
......
...@@ -11,6 +11,7 @@ class ClusterApplicationEntity < Grape::Entity ...@@ -11,6 +11,7 @@ class ClusterApplicationEntity < Grape::Entity
expose :kibana_hostname, if: -> (e, _) { e.respond_to?(:kibana_hostname) } expose :kibana_hostname, if: -> (e, _) { e.respond_to?(:kibana_hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) } expose :email, if: -> (e, _) { e.respond_to?(:email) }
expose :stack, if: -> (e, _) { e.respond_to?(:stack) } expose :stack, if: -> (e, _) { e.respond_to?(:stack) }
expose :modsecurity_enabled, if: -> (e, _) { e.respond_to?(:modsecurity_enabled) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) } expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
expose :can_uninstall?, as: :can_uninstall expose :can_uninstall?, as: :can_uninstall
end end
...@@ -31,6 +31,10 @@ module Clusters ...@@ -31,6 +31,10 @@ module Clusters
application.stack = params[:stack] application.stack = params[:stack]
end end
if application.has_attribute?(:modsecurity_enabled)
application.modsecurity_enabled = params[:modsecurity_enabled] || false
end
if application.respond_to?(:oauth_application) if application.respond_to?(:oauth_application)
application.oauth_application = create_oauth_application(application, request) application.oauth_application = create_oauth_application(application, request)
end end
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'), ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'), ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'),
ingress_mod_security_help_path: help_page_path('user/clusters/applications.md', anchor: 'web-application-firewall-modsecurity'),
environments_help_path: help_page_path('ci/environments', anchor: 'defining-environments'), environments_help_path: help_page_path('ci/environments', anchor: 'defining-environments'),
clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'), clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'),
deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'), deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'),
......
---
title: Add enable_modsecurity setting to managed ingress
merge_request: 21966
author:
type: added
...@@ -248,10 +248,10 @@ use an A record. If your external endpoint is a hostname, use a CNAME record. ...@@ -248,10 +248,10 @@ use an A record. If your external endpoint is a hostname, use a CNAME record.
#### Web Application Firewall (ModSecurity) #### Web Application Firewall (ModSecurity)
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/65192) in GitLab 12.3 (enabled using `ingress_modsecurity` [feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-in-development)). > [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/21966) in GitLab 12.7.
Out of the box, GitLab provides you real-time security monitoring with Out of the box, GitLab provides you real-time security monitoring with
[`modsecurity`](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#modsecurity) [ModSecurity](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#modsecurity).
Modsecurity is a toolkit for real-time web application monitoring, logging, Modsecurity is a toolkit for real-time web application monitoring, logging,
and access control. With GitLab's offering, the [OWASP's Core Rule Set](https://www.modsecurity.org/CRS/Documentation/), which provides generic attack detection capabilities, and access control. With GitLab's offering, the [OWASP's Core Rule Set](https://www.modsecurity.org/CRS/Documentation/), which provides generic attack detection capabilities,
...@@ -267,22 +267,18 @@ This feature: ...@@ -267,22 +267,18 @@ This feature:
kubectl -n gitlab-managed-apps exec -it $(kubectl get pods -n gitlab-managed-apps | grep 'ingress-controller' | awk '{print $1}') -- tail -f /var/log/modsec/audit.log kubectl -n gitlab-managed-apps exec -it $(kubectl get pods -n gitlab-managed-apps | grep 'ingress-controller' | awk '{print $1}') -- tail -f /var/log/modsec/audit.log
``` ```
There is a small performance overhead by enabling `modsecurity`. If this is To enable ModSecurity, check the **Enable Web Application Firewall** checkbox
considered significant for your application, you can either: when installing your [Ingress application](#ingress).
- Disable ModSecurity's rule engine for your deployed application by setting There is a small performance overhead by enabling ModSecurity. If this is
[the deployment variable](../../topics/autodevops/index.md) considered significant for your application, you can disable ModSecurity's
`AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE` to `Off`. This will prevent ModSecurity from rule engine for your deployed application by setting
processing any requests for the given application or environment. [the deployment variable](../../topics/autodevops/index.md)
- Toggle the feature flag to false by running the following command within your `AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE` to `Off`. This will prevent ModSecurity
instance's Rails console: from processing any requests for the given application or environment.
```ruby To permanently disable it, you must [uninstall](#uninstalling-applications) and
Feature.disable(:ingress_modsecurity) reinstall your Ingress application for the changes to take effect.
```
Once disabled, you must [uninstall](#uninstalling-applications) and reinstall your Ingress
application for the changes to take effect.
### JupyterHub ### JupyterHub
......
...@@ -3851,6 +3851,9 @@ msgstr "" ...@@ -3851,6 +3851,9 @@ msgstr ""
msgid "ClusterIntegration|Enable Cloud Run on GKE (beta)" msgid "ClusterIntegration|Enable Cloud Run on GKE (beta)"
msgstr "" msgstr ""
msgid "ClusterIntegration|Enable Web Application Firewall"
msgstr ""
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster." msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
msgstr "" msgstr ""
...@@ -4037,6 +4040,9 @@ msgstr "" ...@@ -4037,6 +4040,9 @@ msgstr ""
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}." msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
msgstr "" msgstr ""
msgid "ClusterIntegration|Learn more about %{startLink}ModSecurity%{endLink}"
msgstr ""
msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}." msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
msgstr "" msgstr ""
......
...@@ -71,6 +71,7 @@ FactoryBot.define do ...@@ -71,6 +71,7 @@ FactoryBot.define do
end end
factory :clusters_applications_ingress, class: Clusters::Applications::Ingress do factory :clusters_applications_ingress, class: Clusters::Applications::Ingress do
modsecurity_enabled { false }
cluster factory: %i(cluster with_installed_helm provided_by_gcp) cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end end
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
"kibana_hostname": { "type": ["string", "null"] }, "kibana_hostname": { "type": ["string", "null"] },
"email": { "type": ["string", "null"] }, "email": { "type": ["string", "null"] },
"stack": { "type": ["string", "null"] }, "stack": { "type": ["string", "null"] },
"modsecurity_enabled": { "type": ["boolean", "null"] },
"update_available": { "type": ["boolean", "null"] }, "update_available": { "type": ["boolean", "null"] },
"can_uninstall": { "type": "boolean" } "can_uninstall": { "type": "boolean" }
}, },
......
...@@ -190,6 +190,7 @@ describe('Applications', () => { ...@@ -190,6 +190,7 @@ describe('Applications', () => {
title: 'Ingress', title: 'Ingress',
status: 'installed', status: 'installed',
externalHostname: 'localhost.localdomain', externalHostname: 'localhost.localdomain',
modsecurity_enabled: false,
}, },
helm: { title: 'Helm Tiller' }, helm: { title: 'Helm Tiller' },
cert_manager: { title: 'Cert-Manager' }, cert_manager: { title: 'Cert-Manager' },
...@@ -473,7 +474,12 @@ describe('Applications', () => { ...@@ -473,7 +474,12 @@ describe('Applications', () => {
vm = mountComponent(Applications, { vm = mountComponent(Applications, {
applications: { applications: {
...APPLICATIONS_MOCK_STATE, ...APPLICATIONS_MOCK_STATE,
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, ingress: {
title: 'Ingress',
status: 'installed',
externalIp: '1.1.1.1',
modsecurity_enabled: false,
},
elastic_stack: { title: 'Elastic Stack', status: 'installed', kibana_hostname: '' }, elastic_stack: { title: 'Elastic Stack', status: 'installed', kibana_hostname: '' },
}, },
}); });
......
...@@ -150,7 +150,7 @@ const DEFAULT_APPLICATION_STATE = { ...@@ -150,7 +150,7 @@ const DEFAULT_APPLICATION_STATE = {
const APPLICATIONS_MOCK_STATE = { const APPLICATIONS_MOCK_STATE = {
helm: { title: 'Helm Tiller', status: 'installable' }, helm: { title: 'Helm Tiller', status: 'installable' },
ingress: { title: 'Ingress', status: 'installable' }, ingress: { title: 'Ingress', status: 'installable', modsecurity_enabled: false },
crossplane: { title: 'Crossplane', status: 'installable', stack: '' }, crossplane: { title: 'Crossplane', status: 'installable', stack: '' },
cert_manager: { title: 'Cert-Manager', status: 'installable' }, cert_manager: { title: 'Cert-Manager', status: 'installable' },
runner: { title: 'GitLab Runner' }, runner: { title: 'GitLab Runner' },
......
...@@ -86,6 +86,7 @@ describe('Clusters Store', () => { ...@@ -86,6 +86,7 @@ describe('Clusters Store', () => {
uninstallSuccessful: false, uninstallSuccessful: false,
uninstallFailed: false, uninstallFailed: false,
validationError: null, validationError: null,
modsecurity_enabled: false,
}, },
runner: { runner: {
title: 'GitLab Runner', title: 'GitLab Runner',
......
...@@ -142,11 +142,11 @@ describe Clusters::Applications::Ingress do ...@@ -142,11 +142,11 @@ describe Clusters::Applications::Ingress do
let(:project) { build(:project) } let(:project) { build(:project) }
let(:cluster) { build(:cluster, projects: [project]) } let(:cluster) { build(:cluster, projects: [project]) }
context 'when ingress_modsecurity is enabled' do context 'when modsecurity_enabled is enabled' do
before do before do
stub_feature_flags(ingress_modsecurity: true)
allow(subject).to receive(:cluster).and_return(cluster) allow(subject).to receive(:cluster).and_return(cluster)
allow(subject).to receive(:modsecurity_enabled).and_return(true)
end end
it 'includes modsecurity module enablement' do it 'includes modsecurity module enablement' do
...@@ -173,10 +173,8 @@ describe Clusters::Applications::Ingress do ...@@ -173,10 +173,8 @@ describe Clusters::Applications::Ingress do
end end
end end
context 'when ingress_modsecurity is disabled' do context 'when modsecurity_enabled is disabled' do
before do before do
stub_feature_flags(ingress_modsecurity: false)
allow(subject).to receive(:cluster).and_return(cluster) allow(subject).to receive(:cluster).and_return(cluster)
end end
......
...@@ -47,6 +47,33 @@ describe Clusters::Applications::CreateService do ...@@ -47,6 +47,33 @@ describe Clusters::Applications::CreateService do
create(:clusters_applications_helm, :installed, cluster: cluster) create(:clusters_applications_helm, :installed, cluster: cluster)
end end
context 'ingress application' do
let(:params) do
{
application: 'ingress',
modsecurity_enabled: true
}
end
before do
expect_any_instance_of(Clusters::Applications::Ingress)
.to receive(:make_scheduled!)
.and_call_original
end
it 'creates the application' do
expect do
subject
cluster.reload
end.to change(cluster, :application_ingress)
end
it 'sets modsecurity_enabled' do
expect(subject.modsecurity_enabled).to eq(true)
end
end
context 'cert manager application' do context 'cert manager application' do
let(:params) do let(:params) do
{ {
......
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