258 lines
9.6 KiB
Go
258 lines
9.6 KiB
Go
// 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, " ")
|
|
}
|