// Copyright (c) 2016-2018 Tigera, Inc. All rights reserved. // 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 model import ( "fmt" "strconv" "strings" apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3" "github.com/projectcalico/libcalico-go/lib/net" "github.com/projectcalico/libcalico-go/lib/numorstring" ) type Rule struct { Action string `json:"action,omitempty"` IPVersion *int `json:"ip_version,omitempty" validate:"omitempty,ipVersion"` Protocol *numorstring.Protocol `json:"protocol,omitempty" validate:"omitempty"` NotProtocol *numorstring.Protocol `json:"!protocol,omitempty" validate:"omitempty"` // ICMP validation notes: 0 is a valid (common) ICMP type and code. Type = 255 is not assigned // to any protocol and the Linux kernel doesn't support matching on it so we validate against // it. ICMPType *int `json:"icmp_type,omitempty" validate:"omitempty,gte=0,lt=255"` ICMPCode *int `json:"icmp_code,omitempty" validate:"omitempty,gte=0,lte=255"` NotICMPType *int `json:"!icmp_type,omitempty" validate:"omitempty,gte=0,lt=255"` NotICMPCode *int `json:"!icmp_code,omitempty" validate:"omitempty,gte=0,lte=255"` SrcTag string `json:"src_tag,omitempty" validate:"omitempty,tag"` SrcNet *net.IPNet `json:"src_net,omitempty" validate:"omitempty"` SrcNets []*net.IPNet `json:"src_nets,omitempty" validate:"omitempty"` SrcSelector string `json:"src_selector,omitempty" validate:"omitempty,selector"` SrcPorts []numorstring.Port `json:"src_ports,omitempty" validate:"omitempty,dive"` DstTag string `json:"dst_tag,omitempty" validate:"omitempty,tag"` DstSelector string `json:"dst_selector,omitempty" validate:"omitempty,selector"` DstNet *net.IPNet `json:"dst_net,omitempty" validate:"omitempty"` DstNets []*net.IPNet `json:"dst_nets,omitempty" validate:"omitempty"` DstPorts []numorstring.Port `json:"dst_ports,omitempty" validate:"omitempty,dive"` NotSrcTag string `json:"!src_tag,omitempty" validate:"omitempty,tag"` NotSrcNet *net.IPNet `json:"!src_net,omitempty" validate:"omitempty"` NotSrcNets []*net.IPNet `json:"!src_nets,omitempty" validate:"omitempty"` NotSrcSelector string `json:"!src_selector,omitempty" validate:"omitempty,selector"` NotSrcPorts []numorstring.Port `json:"!src_ports,omitempty" validate:"omitempty,dive"` NotDstTag string `json:"!dst_tag,omitempty" validate:"omitempty"` NotDstSelector string `json:"!dst_selector,omitempty" validate:"omitempty,selector"` NotDstNet *net.IPNet `json:"!dst_net,omitempty" validate:"omitempty"` NotDstNets []*net.IPNet `json:"!dst_nets,omitempty" validate:"omitempty"` NotDstPorts []numorstring.Port `json:"!dst_ports,omitempty" validate:"omitempty,dive"` // These fields allow us to pass through the raw match criteria from the V3 datamodel unmodified. // The selectors above are formed in the update processor layer by combining the original // selectors, namespace selectors and service account selectors into one. OriginalSrcSelector string `json:"orig_src_selector,omitempty" validate:"omitempty,selector"` OriginalSrcNamespaceSelector string `json:"orig_src_namespace_selector,omitempty" validate:"omitempty,selector"` OriginalDstSelector string `json:"orig_dst_selector,omitempty" validate:"omitempty,selector"` OriginalDstNamespaceSelector string `json:"orig_dst_namespace_selector,omitempty" validate:"omitempty,selector"` OriginalNotSrcSelector string `json:"!orig_src_selector,omitempty" validate:"omitempty,selector"` OriginalNotDstSelector string `json:"!orig_dst_selector,omitempty" validate:"omitempty,selector"` OriginalSrcServiceAccountNames []string `json:"orig_src_service_acct_names,omitempty" validate:"omitempty"` OriginalSrcServiceAccountSelector string `json:"orig_src_service_acct_selector,omitempty" validate:"omitempty,selector"` OriginalDstServiceAccountNames []string `json:"orig_dst_service_acct_names,omitempty" validate:"omitempty"` OriginalDstServiceAccountSelector string `json:"orig_dst_service_acct_selector,omitempty" validate:"omitempty,selector"` // These fields allow us to pass through application layer selectors from the V3 datamodel. HTTPMatch *HTTPMatch `json:"http,omitempty" validate:"omitempty"` LogPrefix string `json:"log_prefix,omitempty" validate:"omitempty"` } type HTTPMatch struct { Methods []string `json:"methods,omitempty" validate:"omitempty"` Paths []apiv3.HTTPPath `json:"paths,omitempty" validate:"omitempty"` } func combineNets(n *net.IPNet, nets []*net.IPNet) []*net.IPNet { if n == nil { return nets } if len(nets) == 0 { return []*net.IPNet{n} } var combination = make([]*net.IPNet, len(nets)+1) copy(combination, nets) combination[len(nets)] = n return combination } func (r Rule) AllSrcNets() []*net.IPNet { return combineNets(r.SrcNet, r.SrcNets) } func (r Rule) AllDstNets() []*net.IPNet { return combineNets(r.DstNet, r.DstNets) } func (r Rule) AllNotSrcNets() []*net.IPNet { return combineNets(r.NotSrcNet, r.NotSrcNets) } func (r Rule) AllNotDstNets() []*net.IPNet { return combineNets(r.NotDstNet, r.NotDstNets) } func joinNets(nets []*net.IPNet) string { parts := make([]string, len(nets)) for i, n := range nets { parts[i] = n.String() } return strings.Join(parts, ",") } func (r Rule) String() string { parts := make([]string, 0) // Action. if r.Action != "" { parts = append(parts, r.Action) } else { parts = append(parts, "Allow") } // Global packet attributes that don't depend on direction. if r.Protocol != nil { parts = append(parts, r.Protocol.String()) } if r.NotProtocol != nil { parts = append(parts, "!"+r.NotProtocol.String()) } if r.ICMPType != nil { parts = append(parts, "type", strconv.Itoa(*r.ICMPType)) } if r.ICMPCode != nil { parts = append(parts, "code", strconv.Itoa(*r.ICMPCode)) } if r.NotICMPType != nil { parts = append(parts, "!type", strconv.Itoa(*r.NotICMPType)) } if r.NotICMPCode != nil { parts = append(parts, "!code", strconv.Itoa(*r.NotICMPCode)) } { // Source attributes. New block ensures that fromParts goes out-of-scope before // we calculate toParts. This prevents copy/paste errors. fromParts := make([]string, 0) if len(r.SrcPorts) > 0 { srcPorts := make([]string, len(r.SrcPorts)) for ii, port := range r.SrcPorts { srcPorts[ii] = port.String() } fromParts = append(fromParts, "ports", strings.Join(srcPorts, ",")) } if r.SrcTag != "" { fromParts = append(fromParts, "tag", r.SrcTag) } if r.SrcSelector != "" { fromParts = append(fromParts, "selector", fmt.Sprintf("%#v", r.SrcSelector)) } srcNets := r.AllSrcNets() if len(srcNets) != 0 { fromParts = append(fromParts, "cidr", joinNets(srcNets)) } if len(r.NotSrcPorts) > 0 { notSrcPorts := make([]string, len(r.NotSrcPorts)) for ii, port := range r.NotSrcPorts { notSrcPorts[ii] = port.String() } fromParts = append(fromParts, "!ports", strings.Join(notSrcPorts, ",")) } if r.NotSrcTag != "" { fromParts = append(fromParts, "!tag", r.NotSrcTag) } if r.NotSrcSelector != "" { fromParts = append(fromParts, "!selector", fmt.Sprintf("%#v", r.NotSrcSelector)) } notSrcNets := r.AllNotSrcNets() if len(notSrcNets) != 0 { fromParts = append(fromParts, "!cidr", joinNets(notSrcNets)) } if len(fromParts) > 0 { parts = append(parts, "from") parts = append(parts, fromParts...) } } { // Destination attributes. toParts := make([]string, 0) if len(r.DstPorts) > 0 { DstPorts := make([]string, len(r.DstPorts)) for ii, port := range r.DstPorts { DstPorts[ii] = port.String() } toParts = append(toParts, "ports", strings.Join(DstPorts, ",")) } if r.DstTag != "" { toParts = append(toParts, "tag", r.DstTag) } if r.DstSelector != "" { toParts = append(toParts, "selector", fmt.Sprintf("%#v", r.DstSelector)) } dstNets := r.AllDstNets() if len(dstNets) != 0 { toParts = append(toParts, "cidr", joinNets(dstNets)) } if len(r.NotDstPorts) > 0 { notDstPorts := make([]string, len(r.NotDstPorts)) for ii, port := range r.NotDstPorts { notDstPorts[ii] = port.String() } toParts = append(toParts, "!ports", strings.Join(notDstPorts, ",")) } if r.NotDstTag != "" { toParts = append(toParts, "!tag", r.NotDstTag) } if r.NotDstSelector != "" { toParts = append(toParts, "!selector", fmt.Sprintf("%#v", r.NotDstSelector)) } notDstNets := r.AllNotDstNets() if len(notDstNets) != 0 { toParts = append(toParts, "!cidr", joinNets(notDstNets)) } // HTTPMatch are destination rules. if r.HTTPMatch != nil { if len(r.HTTPMatch.Methods) > 0 { toParts = append(toParts, "httpMethods", fmt.Sprintf("%+v", r.HTTPMatch.Methods)) } if len(r.HTTPMatch.Paths) > 0 { toParts = append(toParts, "httpPaths", fmt.Sprintf("%+v", r.HTTPMatch.Paths)) } } if len(toParts) > 0 { parts = append(parts, "to") parts = append(parts, toParts...) } } return strings.Join(parts, " ") }