Commit 146c43c9 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'network-policy-editor-create' into 'master'

Implement creation flow in policy editor

See merge request gitlab-org/gitlab!40566
parents 161fc228 e3b9e8db
...@@ -3,6 +3,8 @@ export const EditorModeYAML = 'yaml'; ...@@ -3,6 +3,8 @@ export const EditorModeYAML = 'yaml';
export const RuleTypeNetwork = 'network'; export const RuleTypeNetwork = 'network';
export const RuleActionTypeAllow = 'allow';
export const RuleDirectionInbound = 'ingress'; export const RuleDirectionInbound = 'ingress';
export const RuleDirectionOutbound = 'egress'; export const RuleDirectionOutbound = 'egress';
......
<script> <script>
export default {}; import { GlForm, GlFormSelect, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import { RuleActionTypeAllow } from './constants';
export default {
components: {
GlForm,
GlFormSelect,
GlSprintf,
},
data() {
return { actionType: RuleActionTypeAllow };
},
actionTypes: [{ value: RuleActionTypeAllow, text: s__('NetworkPolicies|Allow') }],
};
</script> </script>
<template> <template>
<div class="gl-bg-gray-100 p-2"></div> <div
class="gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base gl-px-5 gl-pt-5"
>
<gl-form inline>
<gl-sprintf
:message="
s__(
'NetworkPolicies|%{labelStart}Then%{labelEnd} %{action} %{spanStart}the network traffic.%{spanEnd}',
)
"
>
<template #label="{ content }">
<label for="actionType" class="text-uppercase gl-font-lg gl-mr-4 gl-mb-5!">{{
content
}}</label>
</template>
<template #action>
<gl-form-select
id="actionType"
class="gl-mr-4 gl-mb-5!"
:value="actionType"
:options="$options.actionTypes"
/>
</template>
<template #span="{ content }">
<span class="gl-mb-5">{{ content }}</span>
</template>
</gl-sprintf>
</gl-form>
</div>
</template> </template>
<script> <script>
import { mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { import {
GlFormGroup, GlFormGroup,
GlFormSelect, GlFormSelect,
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
GlAlert, GlAlert,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import EnvironmentPicker from '../environment_picker.vue'; import EnvironmentPicker from '../environment_picker.vue';
import NetworkPolicyEditor from '../network_policy_editor.vue'; import NetworkPolicyEditor from '../network_policy_editor.vue';
import PolicyRuleBuilder from './policy_rule_builder.vue'; import PolicyRuleBuilder from './policy_rule_builder.vue';
...@@ -43,6 +44,12 @@ export default { ...@@ -43,6 +44,12 @@ export default {
PolicyPreview, PolicyPreview,
PolicyActionPicker, PolicyActionPicker,
}, },
props: {
threatMonitoringPath: {
type: String,
required: true,
},
},
data() { data() {
return { return {
editorMode: EditorModeRule, editorMode: EditorModeRule,
...@@ -65,6 +72,8 @@ export default { ...@@ -65,6 +72,8 @@ export default {
policyYaml() { policyYaml() {
return toYaml(this.policy); return toYaml(this.policy);
}, },
...mapState('threatMonitoring', ['currentEnvironmentId']),
...mapState('networkPolicies', ['errorUpdatingPolicy']),
shouldShowRuleEditor() { shouldShowRuleEditor() {
return this.editorMode === EditorModeRule; return this.editorMode === EditorModeRule;
}, },
...@@ -80,6 +89,7 @@ export default { ...@@ -80,6 +89,7 @@ export default {
}, },
methods: { methods: {
...mapActions('threatMonitoring', ['fetchEnvironments']), ...mapActions('threatMonitoring', ['fetchEnvironments']),
...mapActions('networkPolicies', ['createPolicy']),
addRule() { addRule() {
this.policy.rules.push(buildRule(RuleTypeEndpoint)); this.policy.rules.push(buildRule(RuleTypeEndpoint));
}, },
...@@ -110,6 +120,15 @@ export default { ...@@ -110,6 +120,15 @@ export default {
this.editorMode = mode; this.editorMode = mode;
}, },
savePolicy() {
const policy = { manifest: toYaml(this.policy) };
return this.createPolicy({
environmentId: this.currentEnvironmentId,
policy,
}).then(() => {
if (!this.errorUpdatingPolicy) redirectTo(this.threatMonitoringPath);
});
},
}, },
policyTypes: [{ value: 'networkPolicy', text: s__('NetworkPolicies|Network Policy') }], policyTypes: [{ value: 'networkPolicy', text: s__('NetworkPolicies|Network Policy') }],
editorModes: [ editorModes: [
...@@ -197,7 +216,7 @@ export default { ...@@ -197,7 +216,7 @@ export default {
@endpoint-labels-change="updateEndpointLabels" @endpoint-labels-change="updateEndpointLabels"
/> />
<div class="gl-p-3 gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100"> <div class="gl-p-3 gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100 gl-mb-5">
<gl-button <gl-button
variant="link" variant="link"
category="primary" category="primary"
...@@ -209,6 +228,7 @@ export default { ...@@ -209,6 +228,7 @@ export default {
</div> </div>
<h4>{{ s__('NetworkPolicies|Actions') }}</h4> <h4>{{ s__('NetworkPolicies|Actions') }}</h4>
<p>{{ s__('NetworkPolicies|Traffic that does not match any rule will be blocked.') }}</p>
<policy-action-picker /> <policy-action-picker />
</div> </div>
<div class="col-sm-12 col-md-6 col-lg-5 col-xl-4"> <div class="col-sm-12 col-md-6 col-lg-5 col-xl-4">
...@@ -238,10 +258,17 @@ export default { ...@@ -238,10 +258,17 @@ export default {
<hr /> <hr />
<div class="row"> <div class="row">
<div class="col-md-auto"> <div class="col-md-auto">
<gl-button type="submit" category="primary" variant="success">{{ <gl-button
s__('NetworkPolicies|Create policy') type="submit"
category="primary"
variant="success"
data-testid="create-policy"
@click="savePolicy"
>{{ s__('NetworkPolicies|Create policy') }}</gl-button
>
<gl-button category="secondary" variant="default" :href="threatMonitoringPath">{{
__('Cancel')
}}</gl-button> }}</gl-button>
<gl-button category="secondary" variant="default">{{ __('Cancel') }}</gl-button>
</div> </div>
</div> </div>
</section> </section>
......
...@@ -133,7 +133,7 @@ export default { ...@@ -133,7 +133,7 @@ export default {
<div <div
class="gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base px-3 pt-3" class="gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base px-3 pt-3"
> >
<gl-form inline> <gl-form inline @submit.prevent>
<gl-sprintf :message="sprintfTemplate"> <gl-sprintf :message="sprintfTemplate">
<template #ifLabel="{ content }"> <template #ifLabel="{ content }">
<label for="ruleType" class="text-uppercase gl-font-lg gl-mr-4 gl-mb-5!">{{ <label for="ruleType" class="text-uppercase gl-font-lg gl-mr-4 gl-mb-5!">{{
......
...@@ -4,7 +4,7 @@ import createStore from './store'; ...@@ -4,7 +4,7 @@ import createStore from './store';
export default () => { export default () => {
const el = document.querySelector('#js-policy-builder-app'); const el = document.querySelector('#js-policy-builder-app');
const { environmentsEndpoint, networkPoliciesEndpoint } = el.dataset; const { environmentsEndpoint, networkPoliciesEndpoint, threatMonitoringPath } = el.dataset;
const store = createStore(); const store = createStore();
store.dispatch('threatMonitoring/setEndpoints', { store.dispatch('threatMonitoring/setEndpoints', {
...@@ -18,7 +18,9 @@ export default () => { ...@@ -18,7 +18,9 @@ export default () => {
el, el,
store, store,
render(createElement) { render(createElement) {
return createElement(PolicyEditorApp, {}); return createElement(PolicyEditorApp, {
props: { threatMonitoringPath },
});
}, },
}); });
}; };
...@@ -12,6 +12,7 @@ export default { ...@@ -12,6 +12,7 @@ export default {
state.environments = payload; state.environments = payload;
state.isLoadingEnvironments = false; state.isLoadingEnvironments = false;
state.errorLoadingEnvironments = false; state.errorLoadingEnvironments = false;
if (payload.length > 0) state.currentEnvironmentId = payload[0].id;
}, },
[types.RECEIVE_ENVIRONMENTS_ERROR](state) { [types.RECEIVE_ENVIRONMENTS_ERROR](state) {
state.isLoadingEnvironments = false; state.isLoadingEnvironments = false;
......
...@@ -4,4 +4,5 @@ ...@@ -4,4 +4,5 @@
#js-policy-builder-app{ data: { network_policies_endpoint: project_security_network_policies_path(@project), #js-policy-builder-app{ data: { network_policies_endpoint: project_security_network_policies_path(@project),
environments_endpoint: project_environments_path(@project), environments_endpoint: project_environments_path(@project),
threat_monitoring_path: project_threat_monitoring_path(@project),
} } } }
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PolicyActionPicker component renders policy action picker 1`] = `
<div
class="gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base gl-px-5 gl-pt-5"
>
<gl-form-stub
inline=""
>
<gl-sprintf-stub
message="%{labelStart}Then%{labelEnd} %{action} %{spanStart}the network traffic.%{spanEnd}"
/>
</gl-form-stub>
</div>
`;
...@@ -158,7 +158,7 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = ` ...@@ -158,7 +158,7 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = `
</h4> </h4>
<div <div
class="gl-p-3 gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100" class="gl-p-3 gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100 gl-mb-5"
> >
<gl-button-stub <gl-button-stub
category="primary" category="primary"
...@@ -175,6 +175,10 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = ` ...@@ -175,6 +175,10 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = `
Actions Actions
</h4> </h4>
<p>
Traffic that does not match any rule will be blocked.
</p>
<policy-action-picker-stub /> <policy-action-picker-stub />
</div> </div>
...@@ -213,6 +217,7 @@ spec: ...@@ -213,6 +217,7 @@ spec:
> >
<gl-button-stub <gl-button-stub
category="primary" category="primary"
data-testid="create-policy"
icon="" icon=""
size="medium" size="medium"
type="submit" type="submit"
...@@ -223,6 +228,7 @@ spec: ...@@ -223,6 +228,7 @@ spec:
<gl-button-stub <gl-button-stub
category="secondary" category="secondary"
href="/threat-monitoring"
icon="" icon=""
size="medium" size="medium"
variant="default" variant="default"
......
import { shallowMount } from '@vue/test-utils';
import PolicyActionPicker from 'ee/threat_monitoring/components/policy_editor/policy_action_picker.vue';
describe('PolicyActionPicker component', () => {
let wrapper;
const factory = ({ propsData } = {}) => {
wrapper = shallowMount(PolicyActionPicker, {
propsData: {
...propsData,
},
});
};
beforeEach(() => {
factory();
});
afterEach(() => {
wrapper.destroy();
});
it('renders policy action picker', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
...@@ -12,6 +12,10 @@ import { ...@@ -12,6 +12,10 @@ import {
EndpointMatchModeLabel, EndpointMatchModeLabel,
} from 'ee/threat_monitoring/components/policy_editor/constants'; } from 'ee/threat_monitoring/components/policy_editor/constants';
import fromYaml from 'ee/threat_monitoring/components/policy_editor/lib/from_yaml'; import fromYaml from 'ee/threat_monitoring/components/policy_editor/lib/from_yaml';
import toYaml from 'ee/threat_monitoring/components/policy_editor/lib/to_yaml';
import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
describe('PolicyEditorApp component', () => { describe('PolicyEditorApp component', () => {
let store; let store;
...@@ -22,9 +26,15 @@ describe('PolicyEditorApp component', () => { ...@@ -22,9 +26,15 @@ describe('PolicyEditorApp component', () => {
Object.assign(store.state.threatMonitoring, { Object.assign(store.state.threatMonitoring, {
...state, ...state,
}); });
Object.assign(store.state.networkPolicies, {
...state,
});
jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());
wrapper = shallowMount(PolicyEditorApp, { wrapper = shallowMount(PolicyEditorApp, {
propsData: { propsData: {
threatMonitoringPath: '/threat-monitoring',
...propsData, ...propsData,
}, },
store, store,
...@@ -45,7 +55,6 @@ describe('PolicyEditorApp component', () => { ...@@ -45,7 +55,6 @@ describe('PolicyEditorApp component', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('renders the policy editor layout', () => { it('renders the policy editor layout', () => {
...@@ -188,4 +197,32 @@ spec: ...@@ -188,4 +197,32 @@ spec:
expect(findAddRuleButton().props('disabled')).toBe(true); expect(findAddRuleButton().props('disabled')).toBe(true);
}); });
}); });
it('creates policy and redirects to a threat monitoring path', async () => {
wrapper.find("[data-testid='create-policy']").vm.$emit('click');
await wrapper.vm.$nextTick();
expect(store.dispatch).toHaveBeenCalledWith('networkPolicies/createPolicy', {
environmentId: -1,
policy: { manifest: toYaml(wrapper.vm.policy) },
});
expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring');
});
describe('given there is a createPolicy error', () => {
beforeEach(() => {
factory({
state: {
errorUpdatingPolicy: true,
},
});
});
it('it does not redirect', async () => {
wrapper.find("[data-testid='create-policy']").vm.$emit('click');
await wrapper.vm.$nextTick();
expect(redirectTo).not.toHaveBeenCalledWith('/threat-monitoring');
});
});
}); });
...@@ -48,6 +48,10 @@ describe('Threat Monitoring mutations', () => { ...@@ -48,6 +48,10 @@ describe('Threat Monitoring mutations', () => {
it('sets errorLoadingEnvironments to false', () => { it('sets errorLoadingEnvironments to false', () => {
expect(state.errorLoadingEnvironments).toBe(false); expect(state.errorLoadingEnvironments).toBe(false);
}); });
it('sets currentEnvironmentId to 1', () => {
expect(state.currentEnvironmentId).toEqual(1);
});
}); });
describe(types.RECEIVE_ENVIRONMENTS_ERROR, () => { describe(types.RECEIVE_ENVIRONMENTS_ERROR, () => {
......
...@@ -16170,6 +16170,9 @@ msgstr "" ...@@ -16170,6 +16170,9 @@ msgstr ""
msgid "NetworkPolicies|%{ifLabelStart}if%{ifLabelEnd} %{ruleType} %{isLabelStart}is%{isLabelEnd} %{ruleDirection} %{ruleSelector} %{directionLabelStart}and is outbound to a%{directionLabelEnd} %{rule} %{portsLabelStart}on%{portsLabelEnd} %{ports}" msgid "NetworkPolicies|%{ifLabelStart}if%{ifLabelEnd} %{ruleType} %{isLabelStart}is%{isLabelEnd} %{ruleDirection} %{ruleSelector} %{directionLabelStart}and is outbound to a%{directionLabelEnd} %{rule} %{portsLabelStart}on%{portsLabelEnd} %{ports}"
msgstr "" msgstr ""
msgid "NetworkPolicies|%{labelStart}Then%{labelEnd} %{action} %{spanStart}the network traffic.%{spanEnd}"
msgstr ""
msgid "NetworkPolicies|%{number} selected" msgid "NetworkPolicies|%{number} selected"
msgstr "" msgstr ""
...@@ -16191,6 +16194,9 @@ msgstr "" ...@@ -16191,6 +16194,9 @@ msgstr ""
msgid "NetworkPolicies|All selected" msgid "NetworkPolicies|All selected"
msgstr "" msgstr ""
msgid "NetworkPolicies|Allow"
msgstr ""
msgid "NetworkPolicies|Allow all inbound traffic to %{selector} from %{ruleSelector} on %{ports}" msgid "NetworkPolicies|Allow all inbound traffic to %{selector} from %{ruleSelector} on %{ports}"
msgstr "" msgstr ""
...@@ -16299,6 +16305,9 @@ msgstr "" ...@@ -16299,6 +16305,9 @@ msgstr ""
msgid "NetworkPolicies|Status" msgid "NetworkPolicies|Status"
msgstr "" msgstr ""
msgid "NetworkPolicies|Traffic that does not match any rule will be blocked."
msgstr ""
msgid "NetworkPolicies|YAML editor" msgid "NetworkPolicies|YAML editor"
msgstr "" msgstr ""
......
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