Commit e3b9e8db authored by ap4y's avatar ap4y

Implement creation flow in policy editor

This commit implements policy editor integration with an API endpoint
through the existing vuex action.
parent 4a0c6ab9
...@@ -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>
...@@ -212,6 +216,7 @@ spec: ...@@ -212,6 +216,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"
...@@ -222,6 +227,7 @@ spec: ...@@ -222,6 +227,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, () => {
......
...@@ -16167,6 +16167,9 @@ msgstr "" ...@@ -16167,6 +16167,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 ""
...@@ -16188,6 +16191,9 @@ msgstr "" ...@@ -16188,6 +16191,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 ""
...@@ -16296,6 +16302,9 @@ msgstr "" ...@@ -16296,6 +16302,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