-
Notifications
You must be signed in to change notification settings - Fork 228
/
policy.go
379 lines (332 loc) · 12.5 KB
/
policy.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
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) 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]
}