npm/pkg/dataplane/policies/policy.go (272 lines of code) (raw):

package policies import ( "fmt" "strings" "github.com/Azure/azure-container-networking/npm/pkg/dataplane/ipsets" "github.com/Azure/azure-container-networking/npm/util" npmerrors "github.com/Azure/azure-container-networking/npm/util/errors" "k8s.io/klog" ) type NPMNetworkPolicy struct { // Namespace is only used by Linux to construct an iptables comment Namespace string // PolicyKey is a unique combination of "namespace/name" of network policy PolicyKey string // ACLPolicyID is only used in Windows. See aclPolicyID() in policy_windows.go for more info ACLPolicyID string // TODO get rid of PodSelectorIPSets in favor of PodSelectorList (exact same except need to add members field to SetInfo) // PodSelectorIPSets holds the IPSets for the Pod Selector PodSelectorIPSets []*ipsets.TranslatedIPSet // ChildPodSelectorIPSets holds the IPSets that are members of any ipset in PodSelectorIPSets ChildPodSelectorIPSets []*ipsets.TranslatedIPSet // TODO change to slice of pointers // PodSelectorList holds the ipsets from PodSelectorIPSets and info about them to avoid duplication in SrcList and DstList fields in ACLs PodSelectorList []SetInfo // RuleIPSets holds all IPSets generated from policy's rules // and not from pod selector IPSets, including children of a NestedLabelOfPod ipset RuleIPSets []*ipsets.TranslatedIPSet ACLs []*ACLPolicy // podIP is key and endpoint ID as value // Will be populated by dataplane and policy manager PodEndpoints map[string]string } func NewNPMNetworkPolicy(netPolName, netPolNamespace string) *NPMNetworkPolicy { return &NPMNetworkPolicy{ Namespace: netPolNamespace, PolicyKey: fmt.Sprintf("%s/%s", netPolNamespace, netPolName), ACLPolicyID: aclPolicyID(netPolNamespace, netPolName), } } func (netPol *NPMNetworkPolicy) HasCIDRRules() bool { for _, set := range netPol.RuleIPSets { if set.Metadata.Type == ipsets.CIDRBlocks { return true } } return false } func (netPol *NPMNetworkPolicy) AllPodSelectorIPSets() []*ipsets.TranslatedIPSet { return append(netPol.PodSelectorIPSets, netPol.ChildPodSelectorIPSets...) } func (netPol *NPMNetworkPolicy) numACLRulesProducedInKernel() int { numRules := 0 hasIngress := false hasEgress := false for _, aclPolicy := range netPol.ACLs { if aclPolicy.hasIngress() { hasIngress = true numRules++ } if aclPolicy.hasEgress() { hasEgress = true numRules++ } } // both Windows and Linux have an extra ACL rule for ingress and an extra rule for egress if hasIngress { numRules++ } if hasEgress { numRules++ } return numRules } func (netPol *NPMNetworkPolicy) PrettyString() string { if netPol == nil { klog.Infof("NPMNetworkPolicy is nil when trying to print string") return "nil NPMNetworkPolicy" } itemStrings := make([]string, 0, len(netPol.ACLs)) for _, item := range netPol.ACLs { itemStrings = append(itemStrings, item.PrettyString()) } aclArrayString := strings.Join(itemStrings, "\n--\n") podSelectorIPSetString := translatedIPSetsToString(netPol.PodSelectorIPSets) podSelectorListString := infoArrayToString(netPol.PodSelectorList) format := `Namespace/Name: %s PodSelectorIPSets: %s PodSelectorList: %s ACLs: %s` return fmt.Sprintf(format, netPol.PolicyKey, podSelectorIPSetString, podSelectorListString, aclArrayString) } // ACLPolicy equivalent to a single iptable rule in linux // or a single HNS rule in windows type ACLPolicy struct { // Comment is the string attached to rule to identity its representation Comment string // TODO(jungukcho): now I think we do not need to manage SrcList and DstList // We may have just one PeerList to hold since it will depend on direction except for namedPort. // They are exclusive and each SetInfo even have its own direction. // PeerList []SetInfo // SrcList source IPSets condition setinfos SrcList []SetInfo // DstList destination IPSets condition setinfos DstList []SetInfo // Target defines a target in iptables for linux. i,e, Mark, Accept, Drop // in windows, this is either ALLOW or DENY Target Verdict // Direction defines the flow of traffic Direction Direction // DstPorts always holds the destination port information. // The valid value for port must be between 1 and 65535, inclusive // and the endPort must be equal or greater than port. DstPorts Ports // Protocol is the value of traffic protocol Protocol Protocol } // NormalizePolicy helps fill in missed fields in aclPolicy func NormalizePolicy(networkPolicy *NPMNetworkPolicy) { for _, aclPolicy := range networkPolicy.ACLs { if aclPolicy.Protocol == "" { aclPolicy.Protocol = UnspecifiedProtocol } if aclPolicy.DstPorts.EndPort == 0 { aclPolicy.DstPorts.EndPort = aclPolicy.DstPorts.Port } } } func ValidatePolicy(networkPolicy *NPMNetworkPolicy) error { for _, aclPolicy := range networkPolicy.ACLs { if !aclPolicy.hasKnownTarget() { return npmerrors.SimpleError(fmt.Sprintf("ACL policy for NetPol %s has unknown target [%s]", networkPolicy.PolicyKey, aclPolicy.Target)) } if !aclPolicy.hasKnownDirection() { return npmerrors.SimpleError(fmt.Sprintf("ACL policy for NetPol %s has unknown direction [%s]", networkPolicy.PolicyKey, aclPolicy.Direction)) } if !aclPolicy.hasKnownProtocol() { return npmerrors.SimpleError(fmt.Sprintf("ACL policy for NetPol %s has unknown protocol [%s]", networkPolicy.PolicyKey, aclPolicy.Protocol)) } if util.IsWindowsDP() && aclPolicy.Protocol == SCTP { return npmerrors.SimpleError(fmt.Sprintf("ACL policy for NetPol %s has unsupported SCTP protocol on Windows", networkPolicy.PolicyKey)) } if !aclPolicy.satisifiesPortAndProtocolConstraints() { return npmerrors.SimpleError(fmt.Sprintf( "ACL policy for NetPol %s has dst port(s) (Port or Port and EndPort), so must have protocol tcp, udp, udplite, sctp, or dccp but has protocol %s", networkPolicy.PolicyKey, string(aclPolicy.Protocol), )) } if !aclPolicy.DstPorts.isValidRange() { return npmerrors.SimpleError(fmt.Sprintf("ACL policy for NetPol %s has invalid port range in DstPorts (start: %d, end: %d)", networkPolicy.PolicyKey, aclPolicy.DstPorts.Port, aclPolicy.DstPorts.EndPort)) } for _, setInfo := range aclPolicy.SrcList { if !setInfo.hasKnownMatchType() { return npmerrors.SimpleError(fmt.Sprintf("ACL policy for NetPol %s has set %s in SrcList with unknown Match Type", networkPolicy.PolicyKey, setInfo.IPSet.Name)) } } for _, setInfo := range aclPolicy.DstList { if !setInfo.hasKnownMatchType() { return npmerrors.SimpleError(fmt.Sprintf("ACL policy for NetPol %s has set %s in DstList with unknown Match Type", networkPolicy.PolicyKey, setInfo.IPSet.Name)) } } } return nil } func NewACLPolicy(target Verdict, direction Direction) *ACLPolicy { acl := &ACLPolicy{ Target: target, Direction: direction, } return acl } // AddSetInfo is to add setInfo to SrcList or DstList based on direction // except for a setInfo for namedPort since namedPort is always for destination. // TODO(jungukcho): cannot come up with Both Direction. func (aclPolicy *ACLPolicy) AddSetInfo(peerList []SetInfo) { for _, peer := range peerList { // in case peer is a setInfo for namedPort, the peer is always added to DstList in aclPolicy // regardless of direction since namePort is always for destination. if peer.MatchType == DstDstMatch { aclPolicy.DstList = append(aclPolicy.DstList, peer) continue } // add peer into SrcList or DstList based on Direction if aclPolicy.Direction == Ingress { aclPolicy.SrcList = append(aclPolicy.SrcList, peer) } else if aclPolicy.Direction == Egress { aclPolicy.DstList = append(aclPolicy.DstList, peer) } } } func (aclPolicy *ACLPolicy) hasKnownDirection() bool { return aclPolicy.Direction == Ingress || aclPolicy.Direction == Egress || aclPolicy.Direction == Both } func (aclPolicy *ACLPolicy) hasIngress() bool { return aclPolicy.Direction == Ingress || aclPolicy.Direction == Both } func (aclPolicy *ACLPolicy) hasEgress() bool { return aclPolicy.Direction == Egress || aclPolicy.Direction == Both } func (aclPolicy *ACLPolicy) hasKnownProtocol() bool { return aclPolicy.Protocol == TCP || aclPolicy.Protocol == UDP || aclPolicy.Protocol == SCTP || aclPolicy.Protocol == UnspecifiedProtocol } func (aclPolicy *ACLPolicy) hasKnownTarget() bool { return aclPolicy.Target == Allowed || aclPolicy.Target == Dropped } func (aclPolicy *ACLPolicy) satisifiesPortAndProtocolConstraints() bool { // namedports handle protocol constraints return (aclPolicy.hasNamedPort() && aclPolicy.Protocol == UnspecifiedProtocol) || aclPolicy.Protocol != UnspecifiedProtocol || aclPolicy.DstPorts.isUnspecified() } func (aclPolicy *ACLPolicy) hasNamedPort() bool { for _, peer := range aclPolicy.DstList { if peer.IPSet.Type == ipsets.NamedPorts { return true } } return false } func (aclPolicy *ACLPolicy) PrettyString() string { format := `Target:%s Direction:%s Protocol:%s Ports:%+v SrcList: %s DstList: %s` return fmt.Sprintf(format, aclPolicy.Target, aclPolicy.Direction, aclPolicy.Protocol, aclPolicy.DstPorts, infoArrayToString(aclPolicy.SrcList), infoArrayToString(aclPolicy.DstList)) } func infoArrayToString(items []SetInfo) string { itemStrings := make([]string, 0, len(items)) for _, item := range items { itemStrings = append(itemStrings, fmt.Sprintf("{%s}", item.PrettyString())) } return fmt.Sprintf("[%s]", strings.Join(itemStrings, ",")) } func translatedIPSetsToString(items []*ipsets.TranslatedIPSet) string { itemStrings := make([]string, 0, len(items)) for _, item := range items { ipset := ipsets.NewIPSet(item.Metadata) itemStrings = append(itemStrings, fmt.Sprintf("{%s}", ipset.PrettyString())) } return fmt.Sprintf("[%s]", strings.Join(itemStrings, ",")) } // SetInfo helps capture additional details in a matchSet. // Included flag captures the negative or positive match. // Included is true when match set does not have "!". // Included is false when match set have "!". // MatchType captures match direction flags. // For example match set in linux: // ! azure-npm-123 src // "!" this indicates a negative match (Included is false) of an azure-npm-123 // MatchType is "src" type SetInfo struct { IPSet *ipsets.IPSetMetadata Included bool MatchType MatchType } // Ports represents a range of ports. // To specify one port, set Port and EndPort to the same value. // uint16 is used since there are 2^16 - 1 TCP/UDP ports (0 is invalid) // and 2^16 SCTP ports. // NewSetInfo creates SetInfo. func NewSetInfo(name string, setType ipsets.SetType, included bool, matchType MatchType) SetInfo { return SetInfo{ IPSet: ipsets.NewIPSetMetadata(name, setType), Included: included, MatchType: matchType, } } func (info SetInfo) PrettyString() string { return fmt.Sprintf("Name:%s HashedName:%s MatchType:%v Included:%v", info.IPSet.GetPrefixName(), info.IPSet.GetHashedName(), info.MatchType, info.Included) } type Ports struct { Port int32 EndPort int32 } func (portRange *Ports) isValidRange() bool { return portRange.Port <= portRange.EndPort } func (portRange *Ports) isUnspecified() bool { return portRange.Port == 0 } type Direction string const ( // Ingress when packet is entering a container Ingress Direction = "IN" // Egress when packet is leaving a container Egress Direction = "OUT" // Both applies to both directions Both Direction = "BOTH" ) type Verdict string const ( // Allowed is accept in linux Allowed Verdict = "ALLOW" // Dropped is denying a flow Dropped Verdict = "DROP" ) // Protocol can be TCP, UDP, SCTP, or unspecified since they are currently supported in networkpolicy. // Protocol value is case-sensitive (Capital now). // TODO: Need to remove this dependency on case-sensitivity. // NPM is not fully tested with SCTP. type Protocol string const ( // TCP Protocol TCP Protocol = "TCP" // UDP Protocol UDP Protocol = "UDP" // SCTP Protocol SCTP Protocol = "SCTP" // UnspecifiedProtocol leaves protocol unspecified. For a named port, this represents its protocol. Otherwise, this represents any protocol. UnspecifiedProtocol Protocol = "unspecified" ) type MatchType int8 // Possible MatchTypes. const ( SrcMatch MatchType = 0 DstMatch MatchType = 1 // MatchTypes with 2 locations (e.g. DstDst) are for ip and port respectively. DstDstMatch MatchType = 2 // This is used for podSelector under spec. It can be Src or Dst based on existence of ingress or egress rule. EitherMatch MatchType = 3 ) var matchTypeStrings = map[MatchType]string{ SrcMatch: util.IptablesSrcFlag, DstMatch: util.IptablesDstFlag, DstDstMatch: util.IptablesDstFlag + "," + util.IptablesDstFlag, } // match type is only used in Linux func (setInfo *SetInfo) hasKnownMatchType() bool { _, exists := matchTypeStrings[setInfo.MatchType] return exists } func (matchType MatchType) toIPTablesString() string { return matchTypeStrings[matchType] }