Commit 2d04c2cd authored by ap4y's avatar ap4y

Implement humanized helpers for policy editor

This commit implements humanized representation of network policies,
this representation will be used in policy editor's preview. Several
rule related functions were extracted in to re-used module.
parent f989774f
import { sprintf, __, s__ } from '~/locale';
import {
EndpointMatchModeAny,
RuleDirectionInbound,
PortMatchModeAny,
RuleTypeEntity,
RuleTypeCIDR,
RuleTypeFQDN,
} from '../constants';
import {
endpointSelector,
portSelectors,
ruleEndpointSelector,
ruleCIDRList,
ruleFQDNList,
} from './utils';
const strongArgs = { strongOpen: '<strong>', strongClose: '</strong>' };
/*
Return humanizied description for a port matcher of a rule.
*/
function humanizeNetworkPolicyRulePorts(rule) {
const { portMatchMode } = rule;
if (portMatchMode === PortMatchModeAny)
return sprintf(s__('NetworkPolicies|%{strongOpen}any%{strongClose} port'), strongArgs, false);
const portList = portSelectors(rule);
const ports = portList.map(({ port, protocol }) => `${port}/${protocol}`).join(', ');
return sprintf(
s__('NetworkPolicies|ports %{ports}'),
{
ports: `<strong>${ports}</strong>`,
},
false,
);
}
/*
Return humanizied description of an endpoint rule.
*/
function humanizeNetworkPolicyRuleEndpoint({ matchLabels }) {
const matchSelector = ruleEndpointSelector(matchLabels);
const labels = Object.keys(matchSelector)
.map(key => `${key}: ${matchSelector[key]}`)
.join(', ');
return labels.length === 0
? sprintf(s__('NetworkPolicies|%{strongOpen}all%{strongClose} pods'), strongArgs, false)
: sprintf(
s__('NetworkPolicies|pods %{pods}'),
{
pods: `<strong>[${labels}]</strong>`,
},
false,
);
}
/*
Return humanizied description of an entity rule.
*/
function humanizeNetworkPolicyRuleEntity({ entities }) {
const entitiesList = entities.length === 0 ? s__('NetworkPolicies|nowhere') : entities.join(', ');
return `<strong>${entitiesList}</strong>`;
}
/*
Return humanizied description of a cidr rule.
*/
function humanizeNetworkPolicyRuleCIDR({ cidr }) {
const cidrList = ruleCIDRList(cidr);
const cidrs =
cidrList.length === 0 ? s__('NetworkPolicies|all IP addresses') : cidrList.join(', ');
return `<strong>${cidrs}</strong>`;
}
/*
Return humanizied description of a fqdn rule.
*/
function humanizeNetworkPolicyRuleFQDN({ fqdn }) {
const fqdnList = ruleFQDNList(fqdn);
const fqdns = fqdnList.length === 0 ? s__('NetworkPolicies|all DNS names') : fqdnList.join(', ');
return `<strong>${fqdns}</strong>`;
}
/*
Return humanizied description of a rule.
*/
function humanizeNetworkPolicyRule(rule) {
const { ruleType } = rule;
switch (ruleType) {
case RuleTypeEntity:
return humanizeNetworkPolicyRuleEntity(rule);
case RuleTypeCIDR:
return humanizeNetworkPolicyRuleCIDR(rule);
case RuleTypeFQDN:
return humanizeNetworkPolicyRuleFQDN(rule);
default:
return humanizeNetworkPolicyRuleEndpoint(rule);
}
}
/*
Return humanizied description of an endpoint matcher of a policy.
*/
function humanizeEndpointSelector(policy) {
const { endpointMatchMode } = policy;
if (endpointMatchMode === EndpointMatchModeAny)
return sprintf(s__('NetworkPolicies|%{strongOpen}all%{strongClose} pods'), strongArgs, false);
const selector = endpointSelector(policy);
const pods = Object.keys(selector)
.map(key => `${key}: ${selector[key]}`)
.join(', ');
return sprintf(
s__('NetworkPolicies|pods %{pods}'),
{
pods: `<strong>[${pods}]</strong>`,
},
false,
);
}
/*
Return humanizied description of a provided network policy.
*/
export default function humanizeNetworkPolicy(policy) {
const { rules } = policy;
if (rules.length === 0) return s__('NetworkPolicies|Deny all traffic');
const selector = humanizeEndpointSelector(policy);
const humanizedRules = rules.map(rule => {
const { direction } = rule;
const template =
direction === RuleDirectionInbound
? s__(
'NetworkPolicies|Allow all inbound traffic to %{selector} from %{ruleSelector} on %{ports}',
)
: s__(
'NetworkPolicies|Allow all outbound traffic from %{selector} to %{ruleSelector} on %{ports}',
);
const ruleSelector = humanizeNetworkPolicyRule(rule);
const ports = humanizeNetworkPolicyRulePorts(rule);
return sprintf(template, { selector, ruleSelector, ports }, false);
});
return humanizedRules.join(`<br><br>${__('and').toUpperCase()}<br><br>`);
}
......@@ -6,37 +6,23 @@ import {
RuleDirectionInbound,
PortMatchModeAny,
} from '../constants';
import { portSelectors, ruleEndpointSelector, ruleCIDRList, ruleFQDNList } from './utils';
/*
Return kubernetes specification object that is shared by all rule types.
*/
function commonSpec({ portMatchMode, ports }) {
if (portMatchMode === PortMatchModeAny) return {};
const portSelectors = ports.split(/\s/).reduce((acc, item) => {
const [port, protocol = 'tcp'] = item.split('/');
const portNumber = parseInt(port, 10);
if (Number.isNaN(portNumber)) return acc;
acc.push({ port, protocol: protocol.trim().toUpperCase() });
return acc;
}, []);
return { toPorts: [{ ports: portSelectors }] };
function commonSpec(rule) {
const spec = {};
const ports = portSelectors(rule);
if (Object.keys(ports).length > 0) spec.toPorts = [{ ports }];
return spec;
}
/*
Return kubernetes specification object for an endpoint rule.
*/
function ruleEndpointSpec({ direction, matchLabels }) {
const matchSelector = matchLabels.split(/\s/).reduce((acc, item) => {
const [key, value = ''] = item.split(':');
if (key.length === 0) return acc;
acc[key] = value.trim();
return acc;
}, {});
const matchSelector = ruleEndpointSelector(matchLabels);
if (Object.keys(matchSelector).length === 0) return {};
return {
......@@ -63,7 +49,7 @@ function ruleEntitySpec({ direction, entities }) {
Return kubernetes specification object for a cidr rule.
*/
function ruleCIDRSpec({ direction, cidr }) {
const cidrList = cidr.length === 0 ? [] : cidr.split(/\s/);
const cidrList = ruleCIDRList(cidr);
if (cidrList.length === 0) return {};
return {
......@@ -77,7 +63,7 @@ function ruleCIDRSpec({ direction, cidr }) {
function ruleFQDNSpec({ direction, fqdn }) {
if (direction === RuleDirectionInbound) return {};
const fqdnList = fqdn.length === 0 ? [] : fqdn.split(/\s/);
const fqdnList = ruleFQDNList(fqdn);
if (fqdnList.length === 0) return {};
return {
......
import { safeDump } from 'js-yaml';
import { EndpointMatchModeAny } from '../constants';
import { ruleSpec } from './rules';
/*
Convert enpdoint labels provided as a string into a kubernetes selector.
Expected endpointLabels in format "one two:three"
*/
function endpointSelector({ endpointMatchMode, endpointLabels }) {
if (endpointMatchMode === EndpointMatchModeAny) return {};
return endpointLabels.split(/\s/).reduce((acc, item) => {
const [key, value = ''] = item.split(':');
if (key.length === 0) return acc;
acc[key] = value.trim();
return acc;
}, {});
}
import { endpointSelector } from './utils';
/*
Return kubernetes resource specification object for a policy.
......
import { EndpointMatchModeAny, PortMatchModeAny } from '../constants';
/*
Convert enpdoint labels provided as a string into a kubernetes selector.
Expects endpointLabels in format "one two:three"
*/
export function endpointSelector({ endpointMatchMode, endpointLabels }) {
if (endpointMatchMode === EndpointMatchModeAny) return {};
return endpointLabels.split(/\s/).reduce((acc, item) => {
const [key, value = ''] = item.split(':');
if (key.length === 0) return acc;
acc[key] = value.trim();
return acc;
}, {});
}
/*
Convert ports provided as a string into a kubernetes port selectors.
Expects ports in format "80/tcp 81"
*/
export function portSelectors({ portMatchMode, ports }) {
if (portMatchMode === PortMatchModeAny) return {};
return ports.split(/\s/).reduce((acc, item) => {
const [port, protocol = 'tcp'] = item.split('/');
const portNumber = parseInt(port, 10);
if (Number.isNaN(portNumber)) return acc;
acc.push({ port, protocol: protocol.trim().toUpperCase() });
return acc;
}, []);
}
/*
Convert list of labels provided as a string into a kubernetes endpoint selector.
Expects matchLabels in format "one two:three"
*/
export function ruleEndpointSelector(matchLabels) {
return matchLabels.split(/\s/).reduce((acc, item) => {
const [key, value = ''] = item.split(':');
if (key.length === 0) return acc;
acc[key] = value.trim();
return acc;
}, {});
}
/*
Convert list of CIDRs provided as a string into a CIDR list expected by the kubernetes policy.
Expects cidr in format "0.0.0.0/24 1.1.1.1/32"
*/
export function ruleCIDRList(cidr) {
return cidr.length === 0 ? [] : cidr.split(/\s/);
}
/*
Convert list of FQDNs provided as a string into a FQDN list expected be the kubernetes policy.
Expects fqdn in format "one-service.com another-service.com"
*/
export function ruleFQDNList(fqdn) {
return fqdn.length === 0 ? [] : fqdn.split(/\s/);
}
import humanizeNetworkPolicy from 'ee/threat_monitoring/components/policy_editor/lib/humanize';
import { buildRule } from 'ee/threat_monitoring/components/policy_editor/lib/rules';
import {
EndpointMatchModeAny,
EndpointMatchModeLabel,
PortMatchModePortProtocol,
RuleDirectionOutbound,
RuleTypeEntity,
RuleTypeCIDR,
RuleTypeFQDN,
} from 'ee/threat_monitoring/components/policy_editor/constants';
describe('humatnizeNetworkPolicy', () => {
let policy;
let rule;
beforeEach(() => {
rule = buildRule();
policy = {
name: 'test-policy',
endpointMatchMode: EndpointMatchModeAny,
endpointLabels: '',
rules: [rule],
};
});
describe('without rules', () => {
beforeEach(() => {
policy.rules = [];
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual('Deny all traffic');
});
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual(
'Allow all inbound traffic to <strong>all</strong> pods ' +
'from <strong>all</strong> pods ' +
'on <strong>any</strong> port',
);
});
describe('with endpoint labels', () => {
beforeEach(() => {
policy.endpointMatchMode = EndpointMatchModeLabel;
policy.endpointLabels = 'one two:value two:another';
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual(
'Allow all inbound traffic to pods <strong>[one: , two: another]</strong> ' +
'from <strong>all</strong> pods ' +
'on <strong>any</strong> port',
);
});
});
describe('with additional egress rule', () => {
beforeEach(() => {
const anotherRule = buildRule();
anotherRule.direction = RuleDirectionOutbound;
policy.rules.push(anotherRule);
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual(
'Allow all inbound traffic to <strong>all</strong> pods from <strong>all</strong> pods on <strong>any</strong> port' +
'<br><br>AND<br><br>' +
'Allow all outbound traffic from <strong>all</strong> pods to <strong>all</strong> pods on <strong>any</strong> port',
);
});
});
describe('with ports', () => {
beforeEach(() => {
rule.portMatchMode = PortMatchModePortProtocol;
rule.ports = '80 81/udp invalid';
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual(
'Allow all inbound traffic to <strong>all</strong> pods ' +
'from <strong>all</strong> pods ' +
'on ports <strong>80/TCP, 81/UDP</strong>',
);
});
});
describe('with endpoint rule', () => {
beforeEach(() => {
rule.matchLabels = 'one two:value two:another';
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual(
'Allow all inbound traffic to <strong>all</strong> pods ' +
'from pods <strong>[one: , two: another]</strong> ' +
'on <strong>any</strong> port',
);
});
});
describe('with entity rule', () => {
beforeEach(() => {
rule = buildRule(RuleTypeEntity);
rule.entities = ['host', 'world'];
policy.rules = [rule];
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual(
'Allow all inbound traffic to <strong>all</strong> pods ' +
'from <strong>host, world</strong> ' +
'on <strong>any</strong> port',
);
});
});
describe('with cidr rule', () => {
beforeEach(() => {
rule = buildRule(RuleTypeCIDR);
rule.cidr = '0.0.0.0/32 1.1.1.1/24';
policy.rules = [rule];
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual(
'Allow all inbound traffic to <strong>all</strong> pods ' +
'from <strong>0.0.0.0/32, 1.1.1.1/24</strong> ' +
'on <strong>any</strong> port',
);
});
});
describe('with fqdn rule', () => {
beforeEach(() => {
rule = buildRule(RuleTypeFQDN);
rule.fqdn = 'some-service.com another-service.com';
policy.rules = [rule];
});
it('returns policy description', () => {
expect(humanizeNetworkPolicy(policy)).toEqual(
'Allow all inbound traffic to <strong>all</strong> pods ' +
'from <strong>some-service.com, another-service.com</strong> ' +
'on <strong>any</strong> port',
);
});
});
});
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