summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js14
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue1
-rw-r--r--app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue116
-rw-r--r--app/assets/javascripts/clusters/constants.js3
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js5
-rw-r--r--app/controllers/clusters/applications_controller.rb2
-rw-r--r--app/graphql/types/snippet_type.rb8
-rw-r--r--app/models/clusters/applications/ingress.rb14
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/presenters/snippet_presenter.rb8
-rw-r--r--app/serializers/cluster_application_entity.rb1
-rw-r--r--app/services/clusters/applications/base_service.rb4
12 files changed, 160 insertions, 20 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index e20c87ed8a0..cb9c44bc36d 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -256,7 +256,8 @@ export default class Clusters {
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
eventHub.$on('setIngressModSecurityEnabled', data => this.setIngressModSecurityEnabled(data));
- eventHub.$on('resetIngressModSecurityEnabled', id => this.resetIngressModSecurityEnabled(id));
+ eventHub.$on('setIngressModSecurityMode', data => this.setIngressModSecurityMode(data));
+ eventHub.$on('resetIngressModSecurityChanges', id => this.resetIngressModSecurityChanges(id));
// Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
@@ -271,7 +272,8 @@ export default class Clusters {
eventHub.$off('setCrossplaneProviderStack');
eventHub.$off('uninstallApplication');
eventHub.$off('setIngressModSecurityEnabled');
- eventHub.$off('resetIngressModSecurityEnabled');
+ eventHub.$off('setIngressModSecurityMode');
+ eventHub.$off('resetIngressModSecurityChanges');
}
initPolling(method, successCallback, errorCallback) {
@@ -525,8 +527,14 @@ export default class Clusters {
this.store.updateAppProperty(id, 'modsecurity_enabled', modSecurityEnabled);
}
- resetIngressModSecurityEnabled(id) {
+ setIngressModSecurityMode({ id, modSecurityMode }) {
+ this.store.updateAppProperty(id, 'isEditingModSecurityMode', true);
+ this.store.updateAppProperty(id, 'modsecurity_mode', modSecurityMode);
+ }
+
+ resetIngressModSecurityChanges(id) {
this.store.updateAppProperty(id, 'isEditingModSecurityEnabled', false);
+ this.store.updateAppProperty(id, 'isEditingModSecurityMode', false);
}
destroy() {
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 87d190e51c4..576a9bc7743 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -313,6 +313,7 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
:install-failed="applications.ingress.installFailed"
:install-application-request-params="{
modsecurity_enabled: applications.ingress.modsecurity_enabled,
+ modsecurity_mode: applications.ingress.modsecurity_mode,
}"
:uninstallable="applications.ingress.uninstallable"
:uninstall-successful="applications.ingress.uninstallSuccessful"
diff --git a/app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue b/app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue
index 98a783aab6e..6b9a926143d 100644
--- a/app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue
+++ b/app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue
@@ -1,8 +1,17 @@
<script>
-import _ from 'lodash';
-import { __ } from '../../locale';
-import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants';
-import { GlAlert, GlSprintf, GlLink, GlToggle, GlButton } from '@gitlab/ui';
+import { escape as esc } from 'lodash';
+import { s__, __ } from '../../locale';
+import { APPLICATION_STATUS, INGRESS, LOGGING_MODE, BLOCKING_MODE } from '~/clusters/constants';
+import {
+ GlAlert,
+ GlSprintf,
+ GlLink,
+ GlToggle,
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlIcon,
+} from '@gitlab/ui';
import eventHub from '~/clusters/event_hub';
import modSecurityLogo from 'images/cluster_app_logos/modsecurity.png';
@@ -17,6 +26,9 @@ export default {
GlLink,
GlToggle,
GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlIcon,
},
props: {
ingress: {
@@ -28,10 +40,23 @@ export default {
required: false,
default: '',
},
+ modes: {
+ type: Object,
+ required: false,
+ default: () => ({
+ [LOGGING_MODE]: {
+ name: s__('ClusterIntegration|Logging mode'),
+ },
+ [BLOCKING_MODE]: {
+ name: s__('ClusterIntegration|Blocking mode'),
+ },
+ }),
+ },
},
data: () => ({
modSecurityLogo,
- hasValueChanged: false,
+ initialValue: null,
+ initialMode: null,
}),
computed: {
modSecurityEnabled: {
@@ -39,19 +64,30 @@ export default {
return this.ingress.modsecurity_enabled;
},
set(isEnabled) {
+ if (this.initialValue === null) {
+ this.initialValue = this.ingress.modsecurity_enabled;
+ }
eventHub.$emit('setIngressModSecurityEnabled', {
id: INGRESS,
modSecurityEnabled: isEnabled,
});
- if (this.hasValueChanged) {
- this.resetStatus();
- } else {
- this.hasValueChanged = true;
- }
},
},
+ hasValueChanged() {
+ return this.modSecurityEnabledChanged || this.modSecurityModeChanged;
+ },
+ modSecurityEnabledChanged() {
+ return this.initialValue !== null && this.initialValue !== this.ingress.modsecurity_enabled;
+ },
+ modSecurityModeChanged() {
+ return (
+ this.ingress.modsecurity_enabled &&
+ this.initialMode !== null &&
+ this.initialMode !== this.ingress.modsecurity_mode
+ );
+ },
ingressModSecurityDescription() {
- return _.escape(this.ingressModSecurityHelpPath);
+ return esc(this.ingressModSecurityHelpPath);
},
saving() {
return [UPDATING].includes(this.ingress.status);
@@ -73,18 +109,40 @@ export default {
this.saving || (this.hasValueChanged && [INSTALLED, UPDATED].includes(this.ingress.status))
);
},
+ modSecurityModeName() {
+ return this.modes[this.ingress.modsecurity_mode].name;
+ },
},
methods: {
updateApplication() {
eventHub.$emit('updateApplication', {
id: INGRESS,
- params: { modsecurity_enabled: this.ingress.modsecurity_enabled },
+ params: {
+ modsecurity_enabled: this.ingress.modsecurity_enabled,
+ modsecurity_mode: this.ingress.modsecurity_mode,
+ },
});
this.resetStatus();
},
resetStatus() {
- eventHub.$emit('resetIngressModSecurityEnabled', INGRESS);
- this.hasValueChanged = false;
+ if (this.initialMode !== null) {
+ this.ingress.modsecurity_mode = this.initialMode;
+ }
+ if (this.initialValue !== null) {
+ this.ingress.modsecurity_enabled = this.initialValue;
+ }
+ this.initialValue = null;
+ this.initialMode = null;
+ eventHub.$emit('resetIngressModSecurityChanges', INGRESS);
+ },
+ selectMode(modeKey) {
+ if (this.initialMode === null) {
+ this.initialMode = this.ingress.modsecurity_mode;
+ }
+ eventHub.$emit('setIngressModSecurityMode', {
+ id: INGRESS,
+ modSecurityMode: modeKey,
+ });
},
},
};
@@ -144,7 +202,35 @@ export default {
label-position="right"
/>
</div>
- <div v-if="showButtons">
+ <div
+ v-if="ingress.modsecurity_enabled"
+ class="gl-responsive-table-row-layout mt-3"
+ role="row"
+ >
+ <div class="table-section section-wrap" role="gridcell">
+ <strong>
+ {{ s__('ClusterIntegration|Global default') }}
+ <gl-icon name="earth" class="align-text-bottom" />
+ </strong>
+ <div class="form-group">
+ <p class="form-text text-muted">
+ <strong>
+ {{
+ s__(
+ 'ClusterIntegration|Set the global mode for the WAF in this cluster. This can be overridden at the environmental level.',
+ )
+ }}
+ </strong>
+ </p>
+ </div>
+ <gl-dropdown :text="modSecurityModeName" :disabled="saveButtonDisabled">
+ <gl-dropdown-item v-for="(mode, key) in modes" :key="key" @click="selectMode(key)">
+ {{ mode.name }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+ </div>
+ <div v-if="showButtons" class="mt-3">
<gl-button
class="btn-success inline mr-1"
:loading="saving"
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 9f98f170fb0..6c3046fc56b 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -66,3 +66,6 @@ export const APPLICATIONS = [
];
export const INGRESS_DOMAIN_SUFFIX = '.nip.io';
+
+export const LOGGING_MODE = 'logging';
+export const BLOCKING_MODE = 'blocking';
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 00ed939e3b4..d3382fcf9fe 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -53,9 +53,11 @@ export default class ClusterStore {
...applicationInitialState,
title: s__('ClusterIntegration|Ingress'),
modsecurity_enabled: false,
+ modsecurity_mode: null,
externalIp: null,
externalHostname: null,
isEditingModSecurityEnabled: false,
+ isEditingModSecurityMode: false,
updateFailed: false,
},
cert_manager: {
@@ -214,6 +216,9 @@ export default class ClusterStore {
if (!this.state.applications.ingress.isEditingModSecurityEnabled) {
this.state.applications.ingress.modsecurity_enabled = serverAppEntry.modsecurity_enabled;
}
+ if (!this.state.applications.ingress.isEditingModSecurityMode) {
+ this.state.applications.ingress.modsecurity_mode = serverAppEntry.modsecurity_mode;
+ }
} else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email;
diff --git a/app/controllers/clusters/applications_controller.rb b/app/controllers/clusters/applications_controller.rb
index 51ddbe2beb4..ba62cfeea7e 100644
--- a/app/controllers/clusters/applications_controller.rb
+++ b/app/controllers/clusters/applications_controller.rb
@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
def cluster_application_params
- params.permit(:application, :hostname, :email, :stack, :modsecurity_enabled)
+ params.permit(:application, :hostname, :email, :stack, :modsecurity_enabled, :modsecurity_mode)
end
def cluster_application_destroy_params
diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb
index cb0bd5205b0..7affcae9f8f 100644
--- a/app/graphql/types/snippet_type.rb
+++ b/app/graphql/types/snippet_type.rb
@@ -65,6 +65,14 @@ module Types
calls_gitaly: true,
null: false
+ field :ssh_url_to_repo, type: GraphQL::STRING_TYPE,
+ description: 'SSH URL to the snippet repository',
+ null: true
+
+ field :http_url_to_repo, type: GraphQL::STRING_TYPE,
+ description: 'HTTP URL to the snippet repository',
+ null: true
+
markdown_field :description_html, null: true, method: :description
end
end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 64659208315..78c2a74da33 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -6,6 +6,9 @@ module Clusters
VERSION = '1.29.7'
INGRESS_CONTAINER_NAME = 'nginx-ingress-controller'
MODSECURITY_LOG_CONTAINER_NAME = 'modsecurity-log'
+ MODSECURITY_MODE_LOGGING = "DetectionOnly"
+ MODSECURITY_MODE_BLOCKING = "On"
+ MODSECURITY_OWASP_RULES_FILE = "/etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf"
self.table_name = 'clusters_applications_ingress'
@@ -18,11 +21,14 @@ module Clusters
default_value_for :ingress_type, :nginx
default_value_for :modsecurity_enabled, true
default_value_for :version, VERSION
+ default_value_for :modsecurity_mode, :logging
enum ingress_type: {
nginx: 1
}
+ enum modsecurity_mode: { logging: 0, blocking: 1 }
+
FETCH_IP_ADDRESS_DELAY = 30.seconds
MODSEC_SIDECAR_INITIAL_DELAY_SECONDS = 10
@@ -82,7 +88,8 @@ module Clusters
"controller" => {
"config" => {
"enable-modsecurity" => "true",
- "enable-owasp-modsecurity-crs" => "true",
+ "enable-owasp-modsecurity-crs" => "false",
+ "modsecurity-snippet" => modsecurity_snippet_content,
"modsecurity.conf" => modsecurity_config_content
},
"extraContainers" => [
@@ -157,6 +164,11 @@ module Clusters
def application_jupyter_nil_or_installable?
cluster.application_jupyter.nil? || cluster.application_jupyter&.installable?
end
+
+ def modsecurity_snippet_content
+ sec_rule_engine = logging? ? MODSECURITY_MODE_LOGGING : MODSECURITY_MODE_BLOCKING
+ "SecRuleEngine #{sec_rule_engine}\nInclude #{MODSECURITY_OWASP_RULES_FILE}"
+ end
end
end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 3000ab7cad0..90221f4e463 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -312,6 +312,10 @@ class Snippet < ApplicationRecord
Digest::SHA256.hexdigest("#{title}#{description}#{created_at}#{updated_at}")
end
+ def versioned_enabled_for?(user)
+ repository_exists? && ::Feature.enabled?(:version_snippets, user)
+ end
+
class << self
# Searches for snippets with a matching title, description or file name.
#
diff --git a/app/presenters/snippet_presenter.rb b/app/presenters/snippet_presenter.rb
index ba9d566932a..ba0b2b42383 100644
--- a/app/presenters/snippet_presenter.rb
+++ b/app/presenters/snippet_presenter.rb
@@ -11,6 +11,14 @@ class SnippetPresenter < Gitlab::View::Presenter::Delegated
Gitlab::UrlBuilder.build(snippet, raw: true)
end
+ def ssh_url_to_repo
+ snippet.ssh_url_to_repo if snippet.versioned_enabled_for?(current_user)
+ end
+
+ def http_url_to_repo
+ snippet.http_url_to_repo if snippet.versioned_enabled_for?(current_user)
+ end
+
def can_read_snippet?
can_access_resource?("read")
end
diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb
index ac59a9df9e5..c08691c6bcf 100644
--- a/app/serializers/cluster_application_entity.rb
+++ b/app/serializers/cluster_application_entity.rb
@@ -15,4 +15,5 @@ class ClusterApplicationEntity < Grape::Entity
expose :can_uninstall?, as: :can_uninstall
expose :available_domains, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:available_domains) }
expose :pages_domain, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:pages_domain) }
+ expose :modsecurity_mode, if: -> (e, _) { e.respond_to?(:modsecurity_mode) }
end
diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb
index a3b39f0994d..bd4ce693085 100644
--- a/app/services/clusters/applications/base_service.rb
+++ b/app/services/clusters/applications/base_service.rb
@@ -31,6 +31,10 @@ module Clusters
application.modsecurity_enabled = params[:modsecurity_enabled] || false
end
+ if application.has_attribute?(:modsecurity_mode)
+ application.modsecurity_mode = params[:modsecurity_mode] || 0
+ end
+
if application.respond_to?(:oauth_application)
application.oauth_application = create_oauth_application(application, request)
end