Commit 037f2a29 authored by ap4y's avatar ap4y

Implement fromYaml helper for loading policy resources

This commit adds a helper function for loading network policies from a
yaml kubernetes manifests into policy object expected by the policy
editor.
parent 378acdbf
......@@ -27,3 +27,5 @@ export const EntityTypes = {
export const PortMatchModeAny = 'any';
export const PortMatchModePortProtocol = 'port/protocol';
export const DisabledByLabel = 'network-policy.gitlab.com/disabled_by';
import { safeLoad } from 'js-yaml';
import { buildRule } from './rules';
import {
DisabledByLabel,
EndpointMatchModeAny,
EndpointMatchModeLabel,
RuleDirectionInbound,
RuleDirectionOutbound,
PortMatchModeAny,
PortMatchModePortProtocol,
RuleTypeEndpoint,
RuleTypeEntity,
RuleTypeCIDR,
RuleTypeFQDN,
} from '../constants';
const rulesFunc = {
[RuleTypeEndpoint](items) {
const labels = items
.reduce(
(acc, { matchLabels }) =>
acc.concat(Object.keys(matchLabels).map(key => `${key}:${matchLabels[key]}`)),
[],
)
.join(' ');
return { matchLabels: labels };
},
[RuleTypeEntity](entities) {
return { entities };
},
[RuleTypeCIDR](items) {
const cidr = items.join(' ');
return { cidr };
},
[RuleTypeFQDN](items) {
const fqdn = items.map(({ matchName }) => matchName).join(' ');
return { fqdn };
},
};
/*
Parse yaml rule into an object expected by the policy editor.
*/
function parseRule(item, direction) {
let ruleItem;
let ruleType;
if (item.fromEntities || item.toEntities) {
ruleType = RuleTypeEntity;
ruleItem = item.fromEntities || item.toEntities;
} else if (item.fromCIDR || item.toCIDR) {
ruleType = RuleTypeCIDR;
ruleItem = item.fromCIDR || item.toCIDR;
} else if (item.toFQDNs) {
ruleType = RuleTypeFQDN;
ruleItem = item.toFQDNs;
} else {
ruleItem = item.fromEndpoints || item.toEndpoints || [];
ruleType = RuleTypeEndpoint;
}
let portMatchMode = PortMatchModeAny;
let portList = [];
if (item.toPorts?.length > 0) {
portMatchMode = PortMatchModePortProtocol;
portList = item.toPorts.reduce(
(acc, { ports }) =>
acc.concat(ports.map(({ port, protocol = 'TCP' }) => `${port}/${protocol.toLowerCase()}`)),
[],
);
}
return {
...buildRule(ruleType, {
direction,
portMatchMode,
ports: portList.join(' '),
}),
...rulesFunc[ruleType](ruleItem),
};
}
/*
Construct a policy object expected by the policy editor from a yaml manifest
*/
export default function fromYaml(manifest) {
const { metadata, spec } = safeLoad(manifest, { json: true });
const { endpointSelector = {}, ingress = [], egress = [] } = spec;
const matchLabels = endpointSelector.matchLabels || {};
const endpointLabels = Object.keys(matchLabels).reduce((acc, key) => {
if (key === DisabledByLabel) return acc;
acc.push(`${key}:${matchLabels[key]}`);
return acc;
}, []);
const rules = []
.concat(
ingress.map(item => parseRule(item, RuleDirectionInbound)),
egress.map(item => parseRule(item, RuleDirectionOutbound)),
)
.filter(rule => Boolean(rule));
return {
name: metadata.name,
description: spec.description,
isEnabled: !Object.keys(matchLabels).includes(DisabledByLabel),
endpointMatchMode: endpointLabels.length > 0 ? EndpointMatchModeLabel : EndpointMatchModeAny,
endpointLabels: endpointLabels.join(' '),
rules,
};
}
import { safeDump } from 'js-yaml';
import { ruleSpec } from './rules';
import { labelSelector } from './utils';
import { EndpointMatchModeAny } from '../constants';
import { EndpointMatchModeAny, DisabledByLabel } from '../constants';
/*
Return kubernetes resource specification object for a policy.
......@@ -26,7 +26,7 @@ function spec({ description, rules, isEnabled, endpointMatchMode, endpointLabels
if (!isEnabled) {
policySpec.endpointSelector.matchLabels = {
...policySpec.endpointSelector.matchLabels,
'network-policy.gitlab.com/disabled_by': 'gitlab',
[DisabledByLabel]: 'gitlab',
};
}
......
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 { buildRule } from 'ee/threat_monitoring/components/policy_editor/lib/rules';
import {
EndpointMatchModeAny,
EndpointMatchModeLabel,
RuleDirectionInbound,
RuleDirectionOutbound,
PortMatchModeAny,
PortMatchModePortProtocol,
RuleTypeEndpoint,
RuleTypeEntity,
RuleTypeCIDR,
RuleTypeFQDN,
EntityTypes,
} from 'ee/threat_monitoring/components/policy_editor/constants';
describe('fromYaml', () => {
let policy;
const cidrExample = '20.1.1.1/32 20.1.1.2/32';
const portExample = '80 81/udp 82/tcp';
beforeEach(() => {
policy = {
name: 'test-policy',
endpointLabels: '',
rules: [],
isEnabled: true,
};
});
it('returns policy object', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
name: 'test-policy',
isEnabled: true,
endpointMatchMode: EndpointMatchModeAny,
endpointLabels: '',
rules: [],
});
});
describe('when description is not empty', () => {
beforeEach(() => {
policy.description = 'test description';
});
it('returns policy object', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
description: 'test description',
});
});
});
describe('when policy is disabled', () => {
beforeEach(() => {
policy.isEnabled = false;
});
it('returns policy object', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
isEnabled: false,
});
});
});
describe('when endpoint labels are not empty', () => {
it('returns policy object', () => {
// test that duplicated keys are supported
const manifest = `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: test-policy
spec:
endpointSelector:
matchLabels:
one: ''
two: value
two: overwrite
three: ''
four: ''
five: ''
network-policy.gitlab.com/disabled_by: gitlab
`;
expect(fromYaml(manifest)).toMatchObject({
endpointMatchMode: EndpointMatchModeLabel,
endpointLabels: 'one: two:overwrite three: four: five:',
});
});
});
describe('with an inbound endpoint rule', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeEndpoint);
rule.matchLabels = 'one two:value two:overwrite';
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
ruleType: RuleTypeEndpoint,
direction: RuleDirectionInbound,
matchLabels: 'one: two:overwrite',
portMatchMode: PortMatchModeAny,
ports: '',
},
],
});
});
});
describe('with an outbound endpoint rule', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeEndpoint);
rule.matchLabels = 'one two:value two:overwrite';
rule.direction = RuleDirectionOutbound;
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
ruleType: RuleTypeEndpoint,
direction: RuleDirectionOutbound,
matchLabels: 'one: two:overwrite',
},
],
});
});
});
describe('with an inbound entity rule', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeEntity);
rule.entities = [EntityTypes.HOST, EntityTypes.WORLD];
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
ruleType: RuleTypeEntity,
direction: RuleDirectionInbound,
entities: [EntityTypes.HOST, EntityTypes.WORLD],
},
],
});
});
});
describe('with an outbound entity rule', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeEntity);
rule.entities = [EntityTypes.HOST, EntityTypes.WORLD];
rule.direction = RuleDirectionOutbound;
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
ruleType: RuleTypeEntity,
direction: RuleDirectionOutbound,
entities: [EntityTypes.HOST, EntityTypes.WORLD],
},
],
});
});
});
describe('with an inbound cidr rule', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeCIDR);
rule.cidr = cidrExample;
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
ruleType: RuleTypeCIDR,
direction: RuleDirectionInbound,
cidr: cidrExample,
},
],
});
});
});
describe('with an outbound cidr rule', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeCIDR);
rule.cidr = cidrExample;
rule.direction = RuleDirectionOutbound;
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
ruleType: RuleTypeCIDR,
direction: RuleDirectionOutbound,
cidr: cidrExample,
},
],
});
});
});
describe('with an outbound fqdn rule', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeFQDN);
rule.fqdn = 'remote-service.com another-service.com';
rule.direction = RuleDirectionOutbound;
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
ruleType: RuleTypeFQDN,
direction: RuleDirectionOutbound,
fqdn: 'remote-service.com another-service.com',
},
],
});
});
});
describe('with an empty inbound rule and port matcher', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeEndpoint);
rule.portMatchMode = PortMatchModePortProtocol;
rule.ports = portExample;
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
portMatchMode: PortMatchModePortProtocol,
ports: '80/tcp 81/udp 82/tcp',
},
],
});
});
});
describe('with an empty outbound rule and port matcher', () => {
beforeEach(() => {
const rule = buildRule(RuleTypeEndpoint);
rule.portMatchMode = PortMatchModePortProtocol;
rule.ports = portExample;
rule.direction = RuleDirectionOutbound;
policy.rules = [rule];
});
it('returns yaml representation', () => {
expect(fromYaml(toYaml(policy))).toMatchObject({
rules: [
{
portMatchMode: PortMatchModePortProtocol,
ports: '80/tcp 81/udp 82/tcp',
},
],
});
});
});
});
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