diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/clusters/clusters_bundle.js | 14 | ||||
-rw-r--r-- | app/assets/javascripts/clusters/components/applications.vue | 1 | ||||
-rw-r--r-- | app/assets/javascripts/clusters/components/ingress_modsecurity_settings.vue | 116 | ||||
-rw-r--r-- | app/assets/javascripts/clusters/constants.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/clusters/stores/clusters_store.js | 5 | ||||
-rw-r--r-- | app/controllers/clusters/applications_controller.rb | 2 | ||||
-rw-r--r-- | app/graphql/types/snippet_type.rb | 8 | ||||
-rw-r--r-- | app/models/clusters/applications/ingress.rb | 14 | ||||
-rw-r--r-- | app/models/snippet.rb | 4 | ||||
-rw-r--r-- | app/presenters/snippet_presenter.rb | 8 | ||||
-rw-r--r-- | app/serializers/cluster_application_entity.rb | 1 | ||||
-rw-r--r-- | app/services/clusters/applications/base_service.rb | 4 |
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 |