-
Notifications
You must be signed in to change notification settings - Fork 208
/
features.go
591 lines (496 loc) · 17.2 KB
/
features.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium
package check
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/blang/semver/v4"
"golang.org/x/exp/maps"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/cilium/cilium/api/v1/models"
"github.com/cilium/cilium/pkg/option"
"github.com/cilium/cilium/pkg/versioncheck"
"github.com/cilium/cilium-cli/defaults"
"github.com/cilium/cilium-cli/internal/utils"
"github.com/cilium/cilium-cli/k8s"
)
// Feature is the name of a Cilium feature (e.g. l7-proxy, cni chaining mode etc)
type Feature string
const (
FeatureCNIChaining Feature = "cni-chaining"
FeatureMonitorAggregation Feature = "monitor-aggregation"
FeatureL7Proxy Feature = "l7-proxy"
FeatureHostFirewall Feature = "host-firewall"
FeatureICMPPolicy Feature = "icmp-policy"
FeatureTunnel Feature = "tunnel"
FeatureEndpointRoutes Feature = "endpoint-routes"
FeatureKPRMode Feature = "kpr-mode"
FeatureKPRExternalIPs Feature = "kpr-external-ips"
FeatureKPRGracefulTermination Feature = "kpr-graceful-termination"
FeatureKPRHostPort Feature = "kpr-hostport"
FeatureKPRSocketLB Feature = "kpr-socket-lb"
FeatureKPRNodePort Feature = "kpr-nodeport"
FeatureKPRSessionAffinity Feature = "kpr-session-affinity"
FeatureHostPort Feature = "host-port"
FeatureNodeWithoutCilium Feature = "node-without-cilium"
FeatureHealthChecking Feature = "health-checking"
FeatureEncryptionPod Feature = "encryption-pod"
FeatureEncryptionNode Feature = "encryption-node"
FeatureIPv4 Feature = "ipv4"
FeatureIPv6 Feature = "ipv6"
FeatureFlavor Feature = "flavor"
FeatureSecretBackendK8s Feature = "secret-backend-k8s"
FeatureCNP Feature = "cilium-network-policy"
FeatureKNP Feature = "k8s-network-policy"
// Whether or not CIDR selectors can match node IPs
FeatureCIDRMatchNodes Feature = "cidr-match-nodes"
FeatureAuthSpiffe Feature = "mutual-auth-spiffe"
FeatureIngressController Feature = "ingress-controller"
FeatureEgressGateway Feature = "enable-ipv4-egress-gateway"
)
// FeatureStatus describes the status of a feature. Some features are either
// turned on or off (c.f. Enabled), while others additionally might include a
// Mode string which provides more information about in what mode a
// particular feature is running ((e.g. when running with CNI chaining,
// Enabled will be true, and the Mode string will additionally contain the name
// of the chained CNI).
type FeatureStatus struct {
Enabled bool
Mode string
}
func (s FeatureStatus) String() string {
str := "Disabled"
if s.Enabled {
str = "Enabled"
}
if len(s.Mode) == 0 {
return str
}
return fmt.Sprintf("%s:%s", str, s.Mode)
}
// FeatureSet contains the FeatureStatus of a collection of Features.
type FeatureSet map[Feature]FeatureStatus
// MatchRequirements returns true if the FeatureSet fs satisfies all the
// requirements in reqs. Returns true for empty requirements list.
func (fs FeatureSet) MatchRequirements(reqs ...FeatureRequirement) (bool, string) {
for _, req := range reqs {
status := fs[req.feature]
if req.requiresEnabled && (req.enabled != status.Enabled) {
return false, fmt.Sprintf("feature %s is disabled", req.feature)
}
if req.requiresMode && (req.mode != status.Mode) {
return false, fmt.Sprintf("requires feature %s mode %s, got %s", req.feature, req.mode, status.Mode)
}
}
return true, ""
}
// IPFamilies returns the list of enabled IP families.
func (fs FeatureSet) IPFamilies() []IPFamily {
var families []IPFamily
if match, _ := fs.MatchRequirements(RequireFeatureEnabled(FeatureIPv4)); match {
families = append(families, IPFamilyV4)
}
if match, _ := fs.MatchRequirements(RequireFeatureEnabled(FeatureIPv6)); match {
families = append(families, IPFamilyV6)
}
return families
}
// deriveFeatures derives additional features based on the status of other features
func (fs FeatureSet) deriveFeatures() error {
fs[FeatureHostPort] = FeatureStatus{
// HostPort support can either be enabled via KPR, or via CNI chaining with portmap plugin
Enabled: (fs[FeatureCNIChaining].Enabled && fs[FeatureCNIChaining].Mode == "portmap" &&
// cilium/cilium#12541: Host firewall doesn't work with portmap CNI chaining
!fs[FeatureHostFirewall].Enabled) ||
fs[FeatureKPRHostPort].Enabled,
}
return nil
}
// FeatureRequirement defines a test requirement. A given FeatureSet may or
// may not satisfy this requirement
type FeatureRequirement struct {
feature Feature
requiresEnabled bool
enabled bool
requiresMode bool
mode string
}
// RequireFeatureEnabled constructs a FeatureRequirement which expects the
// feature to be enabled
func RequireFeatureEnabled(feature Feature) FeatureRequirement {
return FeatureRequirement{
feature: feature,
requiresEnabled: true,
enabled: true,
}
}
// RequireFeatureDisabled constructs a FeatureRequirement which expects the
// feature to be disabled
func RequireFeatureDisabled(feature Feature) FeatureRequirement {
return FeatureRequirement{
feature: feature,
requiresEnabled: true,
enabled: false,
}
}
// RequireFeatureMode constructs a FeatureRequirement which expects the feature
// to be in the given mode
func RequireFeatureMode(feature Feature, mode string) FeatureRequirement {
return FeatureRequirement{
feature: feature,
requiresMode: true,
mode: mode,
}
}
func parseBoolStatus(s string) bool {
switch s {
case "Enabled", "enabled", "True", "true":
return true
}
return false
}
// extractFeaturesFromConfigMap extracts features from the Cilium ConfigMap.
// Note that there is no rule regarding if the default value is reflected
// in the ConfigMap or not.
func (fs FeatureSet) extractFeaturesFromConfigMap(ciliumVersion semver.Version, cm *corev1.ConfigMap) {
// CNI chaining.
// Note: This value might be overwritten by extractFeaturesFromCiliumStatus
// if this information is present in `cilium status`
mode := "none"
if v, ok := cm.Data["cni-chaining-mode"]; ok {
mode = v
}
fs[FeatureCNIChaining] = FeatureStatus{
Enabled: mode != "none",
Mode: mode,
}
if versioncheck.MustCompile("<1.14.0")(ciliumVersion) {
mode = "vxlan"
if v, ok := cm.Data["tunnel"]; ok {
mode = v
}
fs[FeatureTunnel] = FeatureStatus{
Enabled: mode != "disabled",
Mode: mode,
}
} else {
mode = "tunnel"
if v, ok := cm.Data["routing-mode"]; ok {
mode = v
}
tunnelProto := "vxlan"
if mode != "native" {
if v, ok := cm.Data["tunnel-protocol"]; ok {
tunnelProto = v
}
}
fs[FeatureTunnel] = FeatureStatus{
Enabled: mode != "native",
Mode: tunnelProto,
}
}
fs[FeatureIPv4] = FeatureStatus{
Enabled: cm.Data["enable-ipv4"] == "true",
}
fs[FeatureIPv6] = FeatureStatus{
Enabled: cm.Data["enable-ipv6"] == "true",
}
fs[FeatureEndpointRoutes] = FeatureStatus{
Enabled: cm.Data["enable-endpoint-routes"] == "true",
}
fs[FeatureAuthSpiffe] = FeatureStatus{
Enabled: cm.Data["mesh-auth-mutual-enabled"] == "true",
}
fs[FeatureIngressController] = FeatureStatus{
Enabled: cm.Data["enable-ingress-controller"] == "true",
}
fs[FeatureEgressGateway] = FeatureStatus{
Enabled: cm.Data["enable-ipv4-egress-gateway"] == "true",
}
fs[FeatureCIDRMatchNodes] = FeatureStatus{
Enabled: strings.Contains(cm.Data["policy-cidr-match-mode"], "nodes"),
}
}
// extractFeaturesFromRuntimeConfig extracts features from the Cilium runtime config.
// The downside of this approach is that the `DaemonConfig` struct is not stable.
// If there are changes to it in the future, we will likely have to maintain
// version-specific copies of the struct in the Cilium-CLI.
func (ct *ConnectivityTest) extractFeaturesFromRuntimeConfig(ctx context.Context, ciliumPod Pod, result FeatureSet) error {
namespace := ciliumPod.Pod.Namespace
stdout, err := ciliumPod.K8sClient.ExecInPod(ctx, namespace, ciliumPod.Pod.Name,
defaults.AgentContainerName, []string{"cat", "/var/run/cilium/state/agent-runtime-config.json"})
if err != nil {
return fmt.Errorf("failed to fetch cilium runtime config: %w", err)
}
cfg := &option.DaemonConfig{}
if err := json.Unmarshal(stdout.Bytes(), cfg); err != nil {
return fmt.Errorf("unmarshaling cilium runtime config json: %w", err)
}
result[FeatureMonitorAggregation] = FeatureStatus{
Enabled: cfg.MonitorAggregation != "none",
Mode: cfg.MonitorAggregation,
}
result[FeatureICMPPolicy] = FeatureStatus{
Enabled: cfg.EnableICMPRules,
}
result[FeatureHealthChecking] = FeatureStatus{
Enabled: cfg.EnableHealthChecking && cfg.EnableEndpointHealthChecking,
}
result[FeatureEncryptionNode] = FeatureStatus{
Enabled: cfg.EncryptNode,
}
isFeatureKNPEnabled, err := ct.isFeatureKNPEnabled(cfg.EnableK8sNetworkPolicy)
if err != nil {
return fmt.Errorf("unable to determine if KNP feature is enabled: %w", err)
}
result[FeatureKNP] = FeatureStatus{
Enabled: isFeatureKNPEnabled,
}
return nil
}
func (fs FeatureSet) extractFeaturesFromNodes(nodesWithoutCilium map[string]struct{}) {
fs[FeatureNodeWithoutCilium] = FeatureStatus{
Enabled: len(nodesWithoutCilium) != 0,
Mode: strings.Join(maps.Keys(nodesWithoutCilium), ","),
}
}
func (ct *ConnectivityTest) extractFeaturesFromClusterRole(ctx context.Context, client *k8s.Client, result FeatureSet) error {
cr, err := client.GetClusterRole(ctx, defaults.AgentClusterRoleName, metav1.GetOptions{})
if err != nil {
return err
}
result[FeatureSecretBackendK8s] = FeatureStatus{
Enabled: canAccessK8sResourceSecret(cr),
}
return nil
}
func canAccessK8sResourceSecret(cr *rbacv1.ClusterRole) bool {
for _, rule := range cr.Rules {
for _, resource := range rule.Resources {
if resource == "secrets" {
return true
}
}
}
return false
}
func (ct *ConnectivityTest) extractFeaturesFromCiliumStatus(ctx context.Context, ciliumPod Pod, result FeatureSet) error {
stdout, err := ciliumPod.K8sClient.ExecInPod(ctx, ciliumPod.Pod.Namespace, ciliumPod.Pod.Name,
defaults.AgentContainerName, []string{"cilium", "status", "-o", "json"})
if err != nil {
return fmt.Errorf("failed to fetch cilium status: %w", err)
}
st := &models.StatusResponse{}
if err := json.Unmarshal(stdout.Bytes(), st); err != nil {
return fmt.Errorf("unmarshaling Cilium stdout json: %w", err)
}
// CNI chaining
mode := ""
if st.CniChaining != nil {
mode = st.CniChaining.Mode
} else {
// Cilium versions prior to v1.12 do not expose the CNI chaining mode in
// cilium status, it's only available in the ConfigMap, which we
// inherit here
mode = result[FeatureCNIChaining].Mode
}
result[FeatureCNIChaining] = FeatureStatus{
Enabled: len(mode) > 0 && mode != "none",
Mode: mode,
}
// L7 Proxy
result[FeatureL7Proxy] = FeatureStatus{
Enabled: st.Proxy != nil,
}
// Host Firewall
status := false
if hf := st.HostFirewall; hf != nil {
status = parseBoolStatus(st.HostFirewall.Mode)
}
result[FeatureHostFirewall] = FeatureStatus{
Enabled: status,
}
// Kube-Proxy Replacement
mode = "Disabled"
if kpr := st.KubeProxyReplacement; kpr != nil {
mode = kpr.Mode
if f := kpr.Features; kpr.Mode != "Disabled" && f != nil {
if f.ExternalIPs != nil {
result[FeatureKPRExternalIPs] = FeatureStatus{Enabled: f.ExternalIPs.Enabled}
}
if f.HostPort != nil {
result[FeatureKPRHostPort] = FeatureStatus{Enabled: f.HostPort.Enabled}
}
if f.GracefulTermination != nil {
result[FeatureKPRGracefulTermination] = FeatureStatus{Enabled: f.GracefulTermination.Enabled}
}
if f.NodePort != nil {
result[FeatureKPRNodePort] = FeatureStatus{Enabled: f.NodePort.Enabled}
}
if f.SessionAffinity != nil {
result[FeatureKPRSessionAffinity] = FeatureStatus{Enabled: f.SessionAffinity.Enabled}
}
if f.SocketLB != nil {
result[FeatureKPRSocketLB] = FeatureStatus{Enabled: f.SocketLB.Enabled}
}
}
}
result[FeatureKPRMode] = FeatureStatus{
Enabled: mode != "Disabled",
Mode: mode,
}
// encryption
mode = "disabled"
if enc := st.Encryption; enc != nil {
mode = strings.ToLower(enc.Mode)
}
result[FeatureEncryptionPod] = FeatureStatus{
Enabled: mode != "disabled",
Mode: mode,
}
return nil
}
func (ct *ConnectivityTest) extractFeaturesFromK8sCluster(ctx context.Context, result FeatureSet) {
flavor := ct.client.AutodetectFlavor(ctx)
result[FeatureFlavor] = FeatureStatus{
Enabled: flavor.Kind.String() != "invalid",
Mode: strings.ToLower(flavor.Kind.String()),
}
}
const ciliumNetworkPolicyCRDName = "ciliumnetworkpolicies.cilium.io"
func (ct *ConnectivityTest) extractFeaturesFromCRDs(ctx context.Context, result FeatureSet) error {
// CNP are deployed by default.
cnpDeployed := true
// Check if CRD Cilium Network Policy is deployed.
_, err := ct.client.GetCRD(ctx, ciliumNetworkPolicyCRDName, metav1.GetOptions{})
if err != nil {
if !k8serrors.IsNotFound(err) {
fmt.Printf("Type of error: %v, %T", err, err)
return fmt.Errorf("unable to retrieve CRD %s: %w", ciliumNetworkPolicyCRDName, err)
}
// Not found it's not deployed.
cnpDeployed = false
}
result[FeatureCNP] = FeatureStatus{
Enabled: cnpDeployed,
}
return nil
}
func (ct *ConnectivityTest) validateFeatureSet(other FeatureSet, source string) {
for key, found := range other {
expected, ok := ct.Features[key]
if !ok {
ct.Warnf("Cilium feature %q found in pod %s, but not in reference set", key, source)
} else {
if expected != found {
ct.Warnf("Cilium feature %q differs in pod %s. Expected %q, found %q", key, source, expected, found)
}
}
}
for key := range ct.Features {
if _, ok := other[key]; !ok {
ct.Warnf("Cilium feature %q not found in pod %s", key, source)
}
}
}
func (ct *ConnectivityTest) detectCiliumVersion(ctx context.Context) error {
if assumeCiliumVersion := ct.Params().AssumeCiliumVersion; assumeCiliumVersion != "" {
ct.Warnf("Assuming Cilium version %s for connectivity tests", assumeCiliumVersion)
var err error
ct.CiliumVersion, err = utils.ParseCiliumVersion(assumeCiliumVersion)
if err != nil {
return err
}
} else if minVersion, err := ct.DetectMinimumCiliumVersion(ctx); err != nil {
ct.Warnf("Unable to detect Cilium version, assuming %v for connectivity tests: %s", defaults.Version, err)
ct.CiliumVersion, err = utils.ParseCiliumVersion(defaults.Version)
if err != nil {
return err
}
} else {
ct.CiliumVersion = *minVersion
}
return nil
}
func (ct *ConnectivityTest) detectFeatures(ctx context.Context) error {
initialized := false
cm, err := ct.client.GetConfigMap(ctx, ct.params.CiliumNamespace, defaults.ConfigMapName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("unable to retrieve ConfigMap %q: %w", defaults.ConfigMapName, err)
}
if cm.Data == nil {
return fmt.Errorf("ConfigMap %q does not contain any configuration", defaults.ConfigMapName)
}
for _, ciliumPod := range ct.ciliumPods {
features := FeatureSet{}
// If unsure from which source to retrieve the information from,
// prefer "CiliumStatus" over "ConfigMap" over "RuntimeConfig".
// See the corresponding functions for more information.
ct.Features.extractFeaturesFromConfigMap(ct.CiliumVersion, cm)
err = ct.extractFeaturesFromRuntimeConfig(ctx, ciliumPod, features)
if err != nil {
return err
}
ct.Features.extractFeaturesFromNodes(ct.nodesWithoutCilium)
err = ct.extractFeaturesFromCiliumStatus(ctx, ciliumPod, features)
if err != nil {
return err
}
err = ct.extractFeaturesFromClusterRole(ctx, ciliumPod.K8sClient, features)
if err != nil {
return err
}
ct.extractFeaturesFromK8sCluster(ctx, features)
err = features.deriveFeatures()
if err != nil {
return err
}
err = ct.extractFeaturesFromCRDs(ctx, features)
if err != nil {
return err
}
if initialized {
ct.validateFeatureSet(features, ciliumPod.Name())
} else {
ct.Features = features
initialized = true
}
}
return nil
}
func (ct *ConnectivityTest) UpdateFeaturesFromNodes(ctx context.Context) error {
if err := ct.getNodes(ctx); err != nil {
return err
}
ct.Features.extractFeaturesFromNodes(ct.nodesWithoutCilium)
return nil
}
func (ct *ConnectivityTest) ForceDisableFeature(feature Feature) {
ct.Features[feature] = FeatureStatus{Enabled: false}
}
// isFeatureKNPEnabled checks if the Kubernetes Network Policy feature is enabled from the configuration.
// Note that the flag appears in Cilium version 1.14, before that it was unable even thought KNPs were present.
func (ct *ConnectivityTest) isFeatureKNPEnabled(enableK8SNetworkPolicy bool) (bool, error) {
switch {
case enableK8SNetworkPolicy:
// Flag is enabled, means the flag exists.
return true, nil
case !enableK8SNetworkPolicy && versioncheck.MustCompile("<1.14.0")(ct.CiliumVersion):
// Flag was always disabled even KNP were activated before Cilium 1.14.
return true, nil
case !enableK8SNetworkPolicy && versioncheck.MustCompile(">=1.14.0")(ct.CiliumVersion):
// Flag is explicitly set to disabled after Cilium 1.14.
return false, nil
default:
return false, fmt.Errorf("cilium version unsupported %s", ct.CiliumVersion.String())
}
}
func canNodeRunCilium(node *corev1.Node) bool {
val, ok := node.ObjectMeta.Labels["cilium.io/no-schedule"]
return !ok || val == "false"
}