Skip to content

Commit

Permalink
policy: Add ICMP and ICMPv6 API with feature flag
Browse files Browse the repository at this point in the history
This commit implements ICMP/ICMPv6 API for CNP and CCNP.
The `icmp` field is added to CNP and CCNP manifests, and policy can be added by setting ICMP type to that field.
Currentlly, this ICMP CNP feature is disabled by default because of the kernel complexity issue, and it is enabled when the "enable-icmp-rule" hidden flag is set.

Example manifest:
```
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "cnp-icmp"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    icmps:
    - fields:
      - type: 8
      # ICMPv6
      - type: 128
        family: IPv6
```

Signed-off-by: Tomoki Sugiura <cheztomo513@gmail.com>
  • Loading branch information
chez-shanpu authored and ti-mo committed Aug 18, 2021
1 parent 98b5fb3 commit 0bb3eed
Show file tree
Hide file tree
Showing 19 changed files with 1,408 additions and 14 deletions.
4 changes: 4 additions & 0 deletions daemon/cmd/daemon_main.go
Expand Up @@ -982,6 +982,10 @@ func init() {
flags.IntSlice(option.VLANBPFBypass, []int{}, "List of explicitly allowed VLAN IDs, '0' id will allow all VLAN IDs")
option.BindEnv(option.VLANBPFBypass)

flags.Bool(option.EnableICMPRules, false, "Enable ICMP-based rule support for Cilium Network Policies")
flags.MarkHidden(option.EnableICMPRules)
option.BindEnv(option.EnableICMPRules)

viper.BindPFlags(flags)
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/defaults/defaults.go
Expand Up @@ -430,4 +430,7 @@ const (
// ExternalClusterIP enables cluster external access to ClusterIP services.
// Defaults to false to retain prior behaviour of not routing external packets to ClusterIPs.
ExternalClusterIP = false

// EnableICMPRules enables ICMP-based rule support for Cilium Network Policies.
EnableICMPRules = false
)

Large diffs are not rendered by default.

274 changes: 274 additions & 0 deletions pkg/k8s/apis/cilium.io/client/crds/v2/ciliumnetworkpolicies.yaml

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions pkg/k8s/apis/cilium.io/utils/utils.go
Expand Up @@ -144,6 +144,10 @@ func parseToCiliumIngressRule(namespace string, es api.EndpointSelector, inRules
retRules[i].ToPorts = make([]api.PortRule, len(ing.ToPorts))
copy(retRules[i].ToPorts, ing.ToPorts)
}
if ing.ICMPs != nil {
retRules[i].ICMPs = make(api.ICMPRules, len(ing.ICMPs))
copy(retRules[i].ICMPs, ing.ICMPs)
}
retRules[i].IngressCommonRule = parseToCiliumIngressCommonRule(namespace, es, ing.IngressCommonRule)
retRules[i].SetAggregatedSelectors()
}
Expand All @@ -161,6 +165,10 @@ func parseToCiliumIngressDenyRule(namespace string, es api.EndpointSelector, inR
retRules[i].ToPorts = make([]api.PortDenyRule, len(ing.ToPorts))
copy(retRules[i].ToPorts, ing.ToPorts)
}
if ing.ICMPs != nil {
retRules[i].ICMPs = make(api.ICMPRules, len(ing.ICMPs))
copy(retRules[i].ICMPs, ing.ICMPs)
}
retRules[i].IngressCommonRule = parseToCiliumIngressCommonRule(namespace, es, ing.IngressCommonRule)
retRules[i].SetAggregatedSelectors()
}
Expand Down Expand Up @@ -224,6 +232,11 @@ func parseToCiliumEgressRule(namespace string, es api.EndpointSelector, inRules
copy(retRules[i].ToPorts, egr.ToPorts)
}

if egr.ICMPs != nil {
retRules[i].ICMPs = make(api.ICMPRules, len(egr.ICMPs))
copy(retRules[i].ICMPs, egr.ICMPs)
}

if egr.ToFQDNs != nil {
retRules[i].ToFQDNs = make([]api.FQDNSelector, len(egr.ToFQDNs))
copy(retRules[i].ToFQDNs, egr.ToFQDNs)
Expand All @@ -247,6 +260,11 @@ func parseToCiliumEgressDenyRule(namespace string, es api.EndpointSelector, inRu
copy(retRules[i].ToPorts, egr.ToPorts)
}

if egr.ICMPs != nil {
retRules[i].ICMPs = make(api.ICMPRules, len(egr.ICMPs))
copy(retRules[i].ICMPs, egr.ICMPs)
}

retRules[i].EgressCommonRule = parseToCiliumEgressCommonRule(namespace, es, egr.EgressCommonRule)
retRules[i].SetAggregatedSelectors()
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/k8s/apis/cilium.io/v2/register.go
Expand Up @@ -23,7 +23,7 @@ const (
//
// Maintainers: Run ./Documentation/check-crd-compat-table.sh for each release
// Developers: Bump patch for each change in the CRD schema.
CustomResourceDefinitionSchemaVersion = "1.23.3"
CustomResourceDefinitionSchemaVersion = "1.23.4"

// CustomResourceDefinitionSchemaVersionKey is key to label which holds the CRD schema version
CustomResourceDefinitionSchemaVersionKey = "io.cilium.k8s.crd.schema.version"
Expand Down
8 changes: 8 additions & 0 deletions pkg/option/config.go
Expand Up @@ -954,6 +954,9 @@ const (
// NetfilterCompatibleMode guarantees the traffic to pass through kernel
// netfilter.
NetfilterCompatibleMode = "netfilter-compatible-mode"

// EnableICMPRules enables ICMP-based rule support for Cilium Network Policies.
EnableICMPRules = "enable-icmp-rules"
)

// Default string arguments
Expand Down Expand Up @@ -1968,6 +1971,9 @@ type DaemonConfig struct {
// EnableL2NeighDiscovery determines if cilium should perform L2 neighbor
// discovery.
EnableL2NeighDiscovery bool

// EnableICMPRules enables ICMP-based rule support for Cilium Network Policies.
EnableICMPRules bool
}

var (
Expand Down Expand Up @@ -2010,6 +2016,7 @@ var (
K8sEnableK8sEndpointSlice: defaults.K8sEnableEndpointSlice,
k8sEnableAPIDiscovery: defaults.K8sEnableAPIDiscovery,
AllocatorListTimeout: defaults.AllocatorListTimeout,
EnableICMPRules: defaults.EnableICMPRules,

k8sEnableLeasesFallbackDiscovery: defaults.K8sEnableLeasesFallbackDiscovery,
APIRateLimit: make(map[string]string),
Expand Down Expand Up @@ -2753,6 +2760,7 @@ func (c *DaemonConfig) Populate() {
c.EndpointGCInterval = viper.GetDuration(EndpointGCInterval)
c.SelectiveRegeneration = viper.GetBool(SelectiveRegeneration)
c.DisableCNPStatusUpdates = viper.GetBool(DisableCNPStatusUpdates)
c.EnableICMPRules = viper.GetBool(EnableICMPRules)
}

func (c *DaemonConfig) populateDevices() {
Expand Down
20 changes: 20 additions & 0 deletions pkg/policy/api/egress.go
Expand Up @@ -158,6 +158,16 @@ type EgressRule struct {
//
// +kubebuilder:validation:Optional
ToFQDNs FQDNSelectorSlice `json:"toFQDNs,omitempty"`

// ICMPs is a list of ICMP rule identified by type number
// which the endpoint subject to the rule is allowed to connect to.
//
// Example:
// Any endpoint with the label "app=httpd" is allowed to initiate
// type 8 ICMP connections.
//
// +kubebuilder:validation:Optional
ICMPs ICMPRules `json:"icmps,omitempty"`
}

// EgressDenyRule contains all rule types which can be applied at egress, i.e.
Expand Down Expand Up @@ -188,6 +198,16 @@ type EgressDenyRule struct {
//
// +kubebuilder:validation:Optional
ToPorts PortDenyRules `json:"toPorts,omitempty"`

// ICMPs is a list of ICMP rule identified by type number
// which the endpoint subject to the rule is not allowed to connect to.
//
// Example:
// Any endpoint with the label "app=httpd" is not allowed to initiate
// type 8 ICMP connections.
//
// +kubebuilder:validation:Optional
ICMPs ICMPRules `json:"icmps,omitempty"`
}

// SetAggregatedSelectors creates a single slice containing all of the following
Expand Down
47 changes: 47 additions & 0 deletions pkg/policy/api/egress_test.go
Expand Up @@ -297,6 +297,53 @@ func (s *PolicyAPITestSuite) TestIsLabelBasedEgress(c *C) {
}
},
},
{
name: "rule-with-icmp",
setupArgs: func() args {
return args{
&EgressRule{
ICMPs: ICMPRules{
{
Fields: []ICMPField{
{
Type: 8,
},
},
},
},
},
}
},
setupWanted: func() wanted {
return wanted{
isLabelBased: true,
}
},
},
{
name: "rule-with-icmp6",
setupArgs: func() args {
return args{
&EgressRule{
ICMPs: ICMPRules{
{
Fields: []ICMPField{
{
Family: IPv6Family,
Type: 128,
},
},
},
},
},
}
},
setupWanted: func() wanted {
return wanted{
isLabelBased: true,
}
},
},
}

for _, tt := range tests {
Expand Down
107 changes: 107 additions & 0 deletions pkg/policy/api/icmp.go
@@ -0,0 +1,107 @@
// Copyright 2021 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import "strconv"

const (
IPv4Family = "IPv4"
IPv6Family = "IPv6"
)

type ICMPRules []ICMPRule

// ICMPRule is a list of ICMP fields.
type ICMPRule struct {
// Fields is a list of ICMP fields.
//
// +kubebuilder:validation:Optional
Fields []ICMPField `json:"fields,omitempty"`
}

// ICMPField is a ICMP field.
type ICMPField struct {
// Family is a IP address version.
// Currently, we support `IPv4` and `IPv6`.
// `IPv4` is set as default.
//
// +kubebuilder:default=IPv4
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Enum=IPv4;IPv6
Family string `json:"family,omitempty"`

// Type is a ICMP-type.
// It should be 0-255 (8bit).
//
// +kubebuilder:validation:Maximum=255
// +kubebuilder:validation:Minimum=0
Type uint8 `json:"type"`
}

// Iterate iterates over all elements of ICMPRules.
func (ir ICMPRules) Iterate(f func(pr Ports) error) error {
for i := range ir {
if err := f(&ir[i]); err != nil {
return err
}
}
return nil
}

// Len returns the length of the elements of ICMPRules.
func (ir ICMPRules) Len() int {
return len(ir)
}

// GetPortProtocols generates PortProtocol slice from ICMPRule and returns it.
func (ir ICMPRule) GetPortProtocols() []PortProtocol {
var pps []PortProtocol
for _, t := range ir.Fields {
pp := t.PortProtocol()
pps = append(pps, *pp)
}
return pps
}

// GetPortRule generates PortRule from ICMPRule and returns it.
func (ir ICMPRule) GetPortRule() *PortRule {
var pps []PortProtocol
for _, t := range ir.Fields {
pp := t.PortProtocol()
pps = append(pps, *pp)
}
pr := PortRule{
Ports: pps,
}
return &pr
}

// PortProtocol translates ICMPType to PortProtocol.
func (i ICMPField) PortProtocol() *PortProtocol {
var proto L4Proto

typeStr := strconv.Itoa(int(i.Type))
if i.Family == IPv6Family {
proto = ProtoICMPv6
} else {
proto = ProtoICMP
}

pr := PortProtocol{
Port: typeStr,
Protocol: proto,
}
return &pr
}
22 changes: 22 additions & 0 deletions pkg/policy/api/ingress.go
Expand Up @@ -105,6 +105,17 @@ type IngressRule struct {
//
// +kubebuilder:validation:Optional
ToPorts PortRules `json:"toPorts,omitempty"`

// ICMPs is a list of ICMP rule identified by type number
// which the endpoint subject to the rule is allowed to
// receive connections on.
//
// Example:
// Any endpoint with the label "app=httpd" can only accept incoming
// type 8 ICMP connections.
//
// +kubebuilder:validation:Optional
ICMPs ICMPRules `json:"icmps,omitempty"`
}

// IngressDenyRule contains all rule types which can be applied at ingress,
Expand Down Expand Up @@ -135,6 +146,17 @@ type IngressDenyRule struct {
//
// +kubebuilder:validation:Optional
ToPorts PortDenyRules `json:"toPorts,omitempty"`

// ICMPs is a list of ICMP rule identified by type number
// which the endpoint subject to the rule is not allowed to
// receive connections on.
//
// Example:
// Any endpoint with the label "app=httpd" can not accept incoming
// type 8 ICMP connections.
//
// +kubebuilder:validation:Optional
ICMPs ICMPRules `json:"icmps,omitempty"`
}

// SetAggregatedSelectors creates a single slice containing all of the following
Expand Down
47 changes: 47 additions & 0 deletions pkg/policy/api/ingress_test.go
Expand Up @@ -178,6 +178,53 @@ func (s *PolicyAPITestSuite) TestIsLabelBasedIngress(c *C) {
}
},
},
{
name: "rule-with-icmp",
setupArgs: func() args {
return args{
&IngressRule{
ICMPs: ICMPRules{
{
Fields: []ICMPField{
{
Type: 8,
},
},
},
},
},
}
},
setupWanted: func() wanted {
return wanted{
isLabelBased: true,
}
},
},
{
name: "rule-with-icmp6",
setupArgs: func() args {
return args{
&IngressRule{
ICMPs: ICMPRules{
{
Fields: []ICMPField{
{
Family: IPv6Family,
Type: 128,
},
},
},
},
},
}
},
setupWanted: func() wanted {
return wanted{
isLabelBased: true,
}
},
},
}

for _, tt := range tests {
Expand Down

0 comments on commit 0bb3eed

Please sign in to comment.