Skip to content

Commit 68cd898

Browse files
committed
Add support for specifying egress IP range as CIDR
We introduce a new helper function `read_egress_range()` which returns the parsed egress range from each entry of `egress_ip_ranges`. This helper either parses field `egress_range` or parses field `egress_cidr` and returns an object with the same fields as `parse_ip_range()` based on the parsed CIDR and optional fields `skip_first` and `skip_last`.
1 parent ff9af79 commit 68cd898

File tree

8 files changed

+115
-23
lines changed

8 files changed

+115
-23
lines changed

component/egress-gateway-policies.jsonnet

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ local policies = com.generateResources(
2020

2121
local egress_ip_policies = std.flattenArrays([
2222
local cfg = params.egress_gateway.egress_ip_ranges[interface_prefix];
23+
local egress_range = egw.read_egress_range(interface_prefix, cfg);
2324
local ns_egress_ips = std.get(cfg, 'namespace_egress_ips', {});
2425
local dest_cidrs = com.renderArray(std.get(cfg, 'destination_cidrs', []));
2526
[
2627
egw.NamespaceEgressPolicy(
2728
interface_prefix,
28-
cfg.egress_range,
29+
'%(start)s - %(end)s' % egress_range,
2930
std.objectValues(std.get(cfg, 'shadow_ranges', {})),
3031
cfg.node_selector,
3132
ns_egress_ips[namespace],

component/egress-gateway-shadow-ranges.libsonnet

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@ local egress_ip_shadow_ranges =
3838
std.join('.', std.split(range.start, '.')[0:3]);
3939

4040
local check_length(hostname, egress_range, range) =
41-
local public_range = ipcalc.parse_ip_range(
42-
egress_range.prefix,
43-
egress_range.config.egress_range
44-
);
41+
local public_range = egw.read_egress_range(egress_range.prefix, egress_range.config);
4542
local public_len = ipcalc.ipval(public_range.end) - ipcalc.ipval(public_range.start);
4643
local shadow_len = ipcalc.ipval(range.end) - ipcalc.ipval(range.start);
4744

@@ -51,7 +48,7 @@ local egress_ip_shadow_ranges =
5148
range.end,
5249
hostname,
5350
egress_range.prefix,
54-
egress_range.config.egress_range,
51+
public_range,
5552
]
5653
else
5754
range;

component/espejote-templates/egress-gateway-self-service.jsonnet

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ local reconcileNamespace(namespace) =
4242
// ownerReference pointing to the namespace, and labeled as managed by
4343
// us) and update the namespace with an informational message.
4444
local range = res.range;
45+
local egress_range = egw.read_egress_range(range.if_prefix, range);
4546
[
4647
egw.NamespaceEgressPolicy(
4748
range.if_prefix,
48-
range.egress_range,
49+
'%(start)s - %(end)s' % egress_range,
4950
std.objectValues(std.get(range, 'shadow_ranges', {})),
5051
range.node_selector,
5152
egress_ip,

component/espejote-templates/egress-gateway.libsonnet

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,42 @@ local espejoteLabel = {
153153
'cilium.syn.tools/managed-by': 'espejote_cilium_namespace-egress-ips',
154154
};
155155

156+
// read_egress_range extracts the egress range from the passed config object,
157+
// either from field `egress_range` or from field `egress_cidr`.
158+
// When reading from field `egress_cidr` the function also respects fields
159+
// `skip_first` and `skip_last` when generating the resulting range object.
160+
local read_egress_range(prefix, config) =
161+
local ekey = std.setDiff(
162+
std.set([ 'egress_range', 'egress_cidr' ]),
163+
std.set(std.objectFields(config))
164+
);
165+
if std.length(ekey) != 1 then
166+
error
167+
'egress_ip_ranges` entries are expected to have exactly one of ' +
168+
'`egress_range` and `egress_cidr`. entry "%s" has %s.' % [
169+
prefix,
170+
if std.length(ekey) == 2 then 'neither'
171+
else if std.length(ekey) == 0 then 'both'
172+
else 'a weird configuration',
173+
]
174+
else
175+
if std.objectHas(config, 'egress_range') then
176+
local erange = ipcalc.parse_ip_range(prefix, config.egress_range);
177+
erange
178+
else
179+
local ecidr = ipcalc.parse_cidr(prefix, config.egress_cidr);
180+
{
181+
start: if std.get(config, 'skip_first', false) then
182+
ecidr.host_min
183+
else
184+
ecidr.network_address,
185+
end: if std.get(config, 'skip_last', false) then
186+
ecidr.host_max
187+
else
188+
ecidr.broadcast_address,
189+
};
190+
191+
156192
// find_egress_range expects a list of egress range objects which contain the
157193
// interface prefix in a field. This list is precomputed by the Commodore
158194
// component and provided to the Espejote template as
@@ -163,7 +199,7 @@ local espejoteLabel = {
163199
local find_egress_range(ranges, egress_ip) =
164200
local eip = ipcalc.ipval(egress_ip);
165201
local check_fn(rspec) =
166-
local range = ipcalc.parse_ip_range(rspec.if_prefix, rspec.egress_range);
202+
local range = read_egress_range(rspec.if_prefix, rspec);
167203
local start = ipcalc.ipval(range.start);
168204
local end = ipcalc.ipval(range.end);
169205
eip >= start && eip <= end;
@@ -172,14 +208,17 @@ local find_egress_range(ranges, egress_ip) =
172208
range: filtered[0],
173209
errmsg: '',
174210
} else {
211+
local read_erange_str(r) =
212+
local er = read_egress_range(r.if_prefix, r);
213+
'%(start)s - %(end)s' % er,
175214
range: null,
176215
errmsg: if std.length(filtered) == 0 then
177-
local eranges = std.join(', ', [ r.egress_range for r in ranges ]);
216+
local eranges = std.join(', ', [ read_erange_str(r) for r in ranges ]);
178217
'No egress range found for %s, available ranges: %s'
179218
% [ egress_ip, eranges ]
180219
else
181220
local eranges = std.join(
182-
', ', [ '%s (%s)' % [ r.if_prefix, r.egress_range ] for r in filtered ]
221+
', ', [ '%s (%s)' % [ r.if_prefix, read_erange_str(r) ] for r in filtered ]
183222
);
184223
'Found multiple egress ranges which contain %s: %s. ' % [ egress_ip, eranges ] +
185224
"Please contact your cluster's administrator to resolve this range overlap",
@@ -191,4 +230,5 @@ local find_egress_range(ranges, egress_ip) =
191230
NamespaceEgressPolicy: NamespaceEgressPolicy,
192231
espejoteLabel: espejoteLabel,
193232
find_egress_range: find_egress_range,
233+
read_egress_range: read_egress_range,
194234
}

docs/modules/ROOT/pages/references/parameters.adoc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,17 @@ default:: `{}`
381381
This parameter allows users to configure `CiliumEgressGatewayPolicy` (or `IsovalentEgressGatewayPolicy`) resources which assign a single egress IP to a namespace according to the design selected in https://kb.vshn.ch/oc4/explanations/decisions/cloudscale-cilium-egressip.html[Floating egress IPs with Cilium on cloudscale].
382382

383383
Each entry in the parameter is intended to describe a group of dummy interfaces that can be used in `CiliumEgressGatewayPolicy` (or `IsovalentEgressGatewayPolicy`) resources.
384-
The component expects that each value is an object with fields `egress_range`, `node_selector`, `namespace_egress_ips`, `shadow_ranges`, `destination_cidrs`, and `bgp_policy_labels`.
384+
The component expects that each value is an object with fields `egress_cidr`, `egress_range`, `node_selector`, `namespace_egress_ips`, `shadow_ranges`, `destination_cidrs`, and `bgp_policy_labels`.
385+
Fields `egress_cidr` and `egress_range` are mutually exclusive.
386+
The component raises an error for entries which set neither or both.
387+
388+
[TIP]
389+
====
390+
When specifying egress ranges with `egress_cidr`, the component also respects fields `skip_first` and `skip_last`.
391+
These fields default to `false`.
392+
When field `skip_first` is set to `true`, the component omits the given CIDR's network address from the egress range.
393+
When field `skip_last` is set to `true`, the component omits the given CIDR's broadcast address from the egress range.
394+
====
385395

386396
NOTE: Field `shadow_ranges` is optional, see the section on <<_shadow_ranges,shadow ranges>> for more details.
387397

tests/egress-gateway.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ parameters:
3535
self_service_namespace_ips: true
3636
egress_ip_ranges:
3737
egress_a:
38-
egress_range: '192.0.2.32 - 192.0.2.63'
38+
egress_range: 192.0.2.32 - 192.0.2.63
3939
node_selector:
4040
node-role.kubernetes.io/infra: ''
4141
namespace_egress_ips:
@@ -49,7 +49,7 @@ parameters:
4949
destination_cidrs: []
5050
egress_b: null
5151
egress_c:
52-
egress_range: '192.0.2.64 - 192.0.2.95'
52+
egress_cidr: 192.0.2.64/27
5353
node_selector:
5454
node-role.kubernetes.io/infra: ''
5555
namespace_egress_ips:
@@ -73,7 +73,8 @@ parameters:
7373
namespace_egress_ips: {}
7474
shadow_ranges: null
7575
egress_f:
76-
egress_range: '192.0.2.160 - 192.0.2.191'
76+
egress_cidr: 192.0.2.160/27
77+
skip_last: true
7778
node_selector:
7879
node-role.kubernetes.io/infra: ''
7980
namespace_egress_ips:

tests/golden/egress-gateway/cilium/cilium/20_namespace_egress_ip_policies.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ metadata:
9191
annotations:
9292
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true,Prune=false
9393
cilium.syn.tools/description: Generated policy to assign BGP egress IP 192.0.2.160
94-
in egress range "egress_f" (192.0.2.160 - 192.0.2.191) to namespace qux.
94+
in egress range "egress_f" (192.0.2.160 - 192.0.2.190) to namespace qux.
9595
cilium.syn.tools/egress-ip: 192.0.2.160
96-
cilium.syn.tools/egress-range: 192.0.2.160 - 192.0.2.191
96+
cilium.syn.tools/egress-range: 192.0.2.160 - 192.0.2.190
9797
cilium.syn.tools/interface-prefix: egress_f
9898
cilium.syn.tools/source-namespace: qux
9999
labels:

tests/golden/egress-gateway/cilium/cilium/40_egress_ip_managed_resource.yaml

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ spec:
9494
"destination_cidrs": [
9595
"203.0.113.0/24"
9696
],
97-
"egress_range": "192.0.2.64 - 192.0.2.95",
97+
"egress_cidr": "192.0.2.64/27",
9898
"if_prefix": "egress_c",
9999
"namespace_egress_ips": {
100100
"baz": "192.0.2.93"
@@ -136,14 +136,15 @@ spec:
136136
"destination_cidrs": [
137137
"203.0.113.0/25"
138138
],
139-
"egress_range": "192.0.2.160 - 192.0.2.191",
139+
"egress_cidr": "192.0.2.160/27",
140140
"if_prefix": "egress_f",
141141
"namespace_egress_ips": {
142142
"qux": "192.0.2.160"
143143
},
144144
"node_selector": {
145145
"node-role.kubernetes.io/infra": ""
146-
}
146+
},
147+
"skip_last": true
147148
}
148149
]
149150
}
@@ -303,6 +304,42 @@ spec:
303304
'cilium.syn.tools/managed-by': 'espejote_cilium_namespace-egress-ips',
304305
};
305306
307+
// read_egress_range extracts the egress range from the passed config object,
308+
// either from field `egress_range` or from field `egress_cidr`.
309+
// When reading from field `egress_cidr` the function also respects fields
310+
// `skip_first` and `skip_last` when generating the resulting range object.
311+
local read_egress_range(prefix, config) =
312+
local ekey = std.setDiff(
313+
std.set([ 'egress_range', 'egress_cidr' ]),
314+
std.set(std.objectFields(config))
315+
);
316+
if std.length(ekey) != 1 then
317+
error
318+
'egress_ip_ranges` entries are expected to have exactly one of ' +
319+
'`egress_range` and `egress_cidr`. entry "%s" has %s.' % [
320+
prefix,
321+
if std.length(ekey) == 2 then 'neither'
322+
else if std.length(ekey) == 0 then 'both'
323+
else 'a weird configuration',
324+
]
325+
else
326+
if std.objectHas(config, 'egress_range') then
327+
local erange = ipcalc.parse_ip_range(prefix, config.egress_range);
328+
erange
329+
else
330+
local ecidr = ipcalc.parse_cidr(prefix, config.egress_cidr);
331+
{
332+
start: if std.get(config, 'skip_first', false) then
333+
ecidr.host_min
334+
else
335+
ecidr.network_address,
336+
end: if std.get(config, 'skip_last', false) then
337+
ecidr.host_max
338+
else
339+
ecidr.broadcast_address,
340+
};
341+
342+
306343
// find_egress_range expects a list of egress range objects which contain the
307344
// interface prefix in a field. This list is precomputed by the Commodore
308345
// component and provided to the Espejote template as
@@ -313,7 +350,7 @@ spec:
313350
local find_egress_range(ranges, egress_ip) =
314351
local eip = ipcalc.ipval(egress_ip);
315352
local check_fn(rspec) =
316-
local range = ipcalc.parse_ip_range(rspec.if_prefix, rspec.egress_range);
353+
local range = read_egress_range(rspec.if_prefix, rspec);
317354
local start = ipcalc.ipval(range.start);
318355
local end = ipcalc.ipval(range.end);
319356
eip >= start && eip <= end;
@@ -322,14 +359,17 @@ spec:
322359
range: filtered[0],
323360
errmsg: '',
324361
} else {
362+
local read_erange_str(r) =
363+
local er = read_egress_range(r.if_prefix, r);
364+
'%(start)s - %(end)s' % er,
325365
range: null,
326366
errmsg: if std.length(filtered) == 0 then
327-
local eranges = std.join(', ', [ r.egress_range for r in ranges ]);
367+
local eranges = std.join(', ', [ read_erange_str(r) for r in ranges ]);
328368
'No egress range found for %s, available ranges: %s'
329369
% [ egress_ip, eranges ]
330370
else
331371
local eranges = std.join(
332-
', ', [ '%s (%s)' % [ r.if_prefix, r.egress_range ] for r in filtered ]
372+
', ', [ '%s (%s)' % [ r.if_prefix, read_erange_str(r) ] for r in filtered ]
333373
);
334374
'Found multiple egress ranges which contain %s: %s. ' % [ egress_ip, eranges ] +
335375
"Please contact your cluster's administrator to resolve this range overlap",
@@ -341,6 +381,7 @@ spec:
341381
NamespaceEgressPolicy: NamespaceEgressPolicy,
342382
espejoteLabel: espejoteLabel,
343383
find_egress_range: find_egress_range,
384+
read_egress_range: read_egress_range,
344385
}
345386
ipcalc.libsonnet: |
346387
// NOTE(sg): This file is symlinked to `component/espejote-templates` in
@@ -581,10 +622,11 @@ spec:
581622
// ownerReference pointing to the namespace, and labeled as managed by
582623
// us) and update the namespace with an informational message.
583624
local range = res.range;
625+
local egress_range = egw.read_egress_range(range.if_prefix, range);
584626
[
585627
egw.NamespaceEgressPolicy(
586628
range.if_prefix,
587-
range.egress_range,
629+
'%(start)s - %(end)s' % egress_range,
588630
std.objectValues(std.get(range, 'shadow_ranges', {})),
589631
range.node_selector,
590632
egress_ip,

0 commit comments

Comments
 (0)