-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Policy simplification #670
Conversation
pkg/policy/api/rule.go
Outdated
// accumulated additively | ||
// | ||
// The rule is split into an ingress section which contains all rules | ||
// applicable at ingress, and an egress section applicable at egress. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
applicable -> enforced
pkg/policy/api/rule.go
Outdated
// accumulated additively | ||
// | ||
// The rule is split into an ingress section which contains all rules | ||
// applicable at ingress, and an egress section applicable at egress. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also specify that for a flow between two endpoints to be allowed by Cilium, it must be allowed by both the egress section of the source endpoint and by the ingress section of the destination endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is only true to some extent. Endpoint to endpoint policy is only specified at Ingress. We only do egress specification for things where we do not control the receiving endpoint.
I've added a note to clarify this.
pkg/policy/api/rule.go
Outdated
// rule. Rules cannot be identified by comment. | ||
// | ||
// +optional | ||
Comment string `json:"comment,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment -> Description (?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I inherited comment from iptables but I like Description better as well, fixed.
pkg/policy/api/rule.go
Outdated
// If omitted, no rules will be applied at ingress via this rule. | ||
// | ||
// +optional | ||
Ingress *IngressRule `json:"ingress,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a slice, not a single rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All members of IngressRule are sliced which saves us to enforce oneOf at IngressRule level. What benefit do you see in one over the over? I'm fine with both actually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the semantics is different.
I would like to be able to specify:
- allow from 10/8 to port 80
and - allow from 11/8 to port 443
That's very different from:
- allow from "either 10/8 or 11/8" to "either port 80 or port 443"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in another comment as well: The datapath does support the coupling of L3 and L4 rules at this point and we have to support the decoupled version anyway for k8s policy.
I'm fine changing this as I said but we can't support the coupling right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually you could. It would just cost you the calculation of a cartesian product of the k8s L3 rules with the K8s L4 rules in the same k8s policy, to obtain those "coupled" rule versions. It may be costly only in case of complicated k8s policies, e.g. with contain many criteria. I don't think that will be common, nor very costly.
pkg/policy/api/rule.go
Outdated
// If omitted, no rules will be applied at egress via this rule | ||
// | ||
// +optional | ||
Egress *EgressRule `json:"egress,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a slice, not a single rule.
pkg/policy/api/rule.go
Outdated
// protocol which the endpoint subject to the rule is allowed to | ||
// connect to. | ||
// | ||
// If omitted and no other IngressRule applies a ToPorts rule on the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why mentioning "IngressRule" here? Did you mean "EgressRule"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant EgressRule but I believe the paragraph is not required at all for understanding.
pkg/policy/api/rule.go
Outdated
// protocol which the endpoint subject to the rule is allowed to | ||
// connect to. | ||
// | ||
// If omitted and no other IngressRule applies a ToPorts rule on the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this confusing. This looks like a conjunction of rules.
I would prefer if all the rules form a disjunction, i.e. if a rule allows some flows, no addition or removal of any other rule can alter that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure which paragraph this comment refers to.
pkg/policy/api/rule.go
Outdated
foo string | ||
} | ||
|
||
// PortProtocol specifices an L4 port with an optional protocol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
specifices -> specifies
protocol -> transport protocol
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
pkg/policy/api/rule.go
Outdated
|
||
// PortProtocol specifices an L4 port with an optional protocol | ||
// | ||
// TODO: allow port range? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's usually useful. Actually, a list combining individual ports and ranges seems common, e.g.:
["80", "443", "2000-2100"]
The only negative aspect about it is that it requires the data to be passed as strings, but that's a small price to pay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I will change it to a string for now so we can easily add ranges later on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about:
type PortProtocol struct {
Port PortRange
}
type PortRange stuct{
min uint16
max uint16
}
func NewPort(p uint16) *PortRange{
return &PortRange{min: p, max: p}
}
func NewPortRange(min, max uint16) *PortRange{
return &PortRange{min: min, max: max}
}
func (p *PortRange) Contains(p uint16) bool {
return p.min >= p && p <= p.max
}
At least we don't deal with strings
pkg/policy/api/rule.go
Outdated
Port uint16 `json:"port"` | ||
|
||
// Protocol is the L4 protocol. If omitted, any protocol matches | ||
// Accepted values: "tcp", "udp", ""/"any" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of ICMP, how can one match on the code / type?
If that's not supported, specify it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note added, we do not support matching on ICMP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about port 0 with proto any?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such a rule would never match right now. I've added the case to the Validate()
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial review comments.
pkg/policy/api/rule.go
Outdated
type Rule struct { | ||
// EndpointSelector selects all endpoints which should be subject to | ||
// this rule. Cannot be empty. | ||
EndpointSelector EndpointSelector `json:"selector"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider renaming this to AppliedEndpoints.
// Mandatory field. Identifies the endpoints where this rule should be applied.
AppliedEndpoints EndpointSelector json:"selector"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like selector because it is in line with the k8s API which uses the word selector for everything to select objects to apply rules onto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Thanks.
pkg/policy/api/rule.go
Outdated
// If omitted, no rules will be applied at ingress via this rule. | ||
// | ||
// +optional | ||
Ingress *IngressRule `json:"ingress,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The field is a pointer to IngressRule, and can hold only 1 ingress rule. The document needs to be fixed, or we need to have a collection of IngressRules here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment fixed
pkg/policy/api/rule.go
Outdated
// | ||
// Either ingres, egress, or both can be provided. If both ingress and egress | ||
// are omitted, the rule has no effect. | ||
type Rule struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider renaming this to Policy. A Policy can contain multiple rules. Each rule can be of type Egress or Ingress.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what it is. The policy is not part of the API. Cilium owns a policy repository which consists of a list of rules which can be modified via the API by adding/removing/changing rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Thanks.
pkg/policy/api/rule.go
Outdated
// If omitted, no rules will be applied at egress via this rule | ||
// | ||
// +optional | ||
Egress *EgressRule `json:"egress,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we simplify this by having a collection of Rules (a new type that only contains match predicates)? A Rule can either be of type Egress or Ingress. The current type Rule can be renamed to Policy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the comment from @rlenglet. I'm trying to avoid oneOf as it can't be described and enforced with swagger.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, thanks.
pkg/policy/api/rule.go
Outdated
// endpoints which are whitelisted in fromCIDR. | ||
// | ||
// +optional | ||
FromEndpoints []EndpointSelector `json:"fromEndpoints,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider renaming this to SourceEndpoints
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source has some implications as it could be confused for source addresses. "From" is clearer in that sense, "rule selects b and allows ingress from a"
pkg/policy/api/rule.go
Outdated
// EndpointSelector which are allowed to communicate with the endpoint | ||
// subject to the rule. | ||
// | ||
// If omitted and no other IngressRule applies a FromEndpoints rule on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this paragraph. We're referring to fromCIDR, which is defined later, and the comment is redundant as the params are tagged as optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, only obfuscates, removed both occurrences.
pkg/policy/api/rule.go
Outdated
// +optional | ||
FromEndpoints []EndpointSelector `json:"fromEndpoints,omitempty"` | ||
|
||
// FromRequires is a selector which must match in combination with at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not understand the purpose of FromRequires. The example about group=A talks about labels, but isn't this field of type endpointselector?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have reworded the paragraph to make it clearer
pkg/policy/api/rule.go
Outdated
// ports. | ||
// | ||
// +optional | ||
ToPorts []PortRule `json:"toPorts,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider renaming to destinationPorts
pkg/policy/api/rule.go
Outdated
// endpoints which are whitelisted in FromEndpoints. | ||
// | ||
// +optional | ||
FromCIDR []CIDR `json:"toCIDR,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider renaming to SourceCIDRs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SourceCIDRs would definitely be mistaken for source address CIDR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2nd thought, this is exactly what we want so I'm making this change.
pkg/policy/api/rule.go
Outdated
// TODO: allow port range? | ||
type PortProtocol struct { | ||
// Port is an L4 port number | ||
Port uint16 `json:"port"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this:
Ports []uint16 ?
That way, one can specify TCP: [80, 8080], as an example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See @rlenglet comment to move to strong to support ranges, I like that approach. We could eventually support all of "10", "10, 11", "10-15"
8fb64f4
to
eaf8c02
Compare
func ParseLabelArray(labels ...string) LabelArray { | ||
array := make([]*Label, len(labels)) | ||
for i := range labels { | ||
array[i] = ParseLabel(labels[i]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if a label is not valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can a label not be valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, since it returns a pointer I was assuming it could be nil
pkg/policy/api/rule.go
Outdated
EndpointSelector EndpointSelector `json:"endpointSelector"` | ||
|
||
// Ingress is a list of IngressRule which are enforced at ingress. | ||
// If omitted, this rule does not apply at ingress. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the omitted
ones please also add the case where they are empty. "If omitted, or empty,..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, will do.
pkg/policy/api/rule.go
Outdated
// unique, multiple rules can have overlapping or identical labels. | ||
// | ||
// +optional | ||
Labels labels.LabelArray `json:"group,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been thinking about this. What about "Metadata" instead, seems wrong?
Also, why the json variable is called Group? I mean, I would like to see Group
instead of Labels
in the go struct.
pkg/policy/api/selector.go
Outdated
// ParseEndpointSelector parses a list of labels in the format of | ||
// strings and returns an EndpointSelector | ||
func ParseEndpointSelector(list ...string) EndpointSelector { | ||
array := make([]*labels.Label, len(list)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[]*labels.Label
-> labels.LabelArray
pkg/policy/api/rule.go
Outdated
// is entering the endpoint selected by the endpointSelector. | ||
// | ||
// - All members of this structure are optional. | ||
// - All rule types are considered independently |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it this means all slices in this structure will be "ORed" or "ANDed"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ORed. Rephrased.
pkg/policy/repository.go
Outdated
switch r.canReach(ctx) { | ||
// The rule contained a constraint which was not met, this | ||
// connection is not allowed | ||
case api.Denied: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get this, if the policy has multiple rules, and one of the rules can't be reachable why does that means it is automatically denied?
Shouldn't you first check for all the rules that have its ingress matching the ctx.To
and then check if one of them allows receiving from ctx.From
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api.Denied
is only returned if it is denied for sure. This only happens for Require rules. If the requirement for a require rule is not met, the potential consumer is never allowed.
pkg/policy/repository.go
Outdated
} | ||
|
||
// AddList inserts a rule into the policy repository | ||
func (p *Repository) AddList(rules []*api.Rule) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why a pointer to the rule itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've masked it behind a new api.Rules
type now so we can easily change this. I don't see a problem with a pointer.
defer p.Mutex.Unlock() | ||
|
||
// Validate entire rule list first and only append array if | ||
// all rules are valid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
||
// GetJSON returns all rules of the policy repository as string in JSON | ||
// representation | ||
func (p *Repository) GetJSON() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not implement the because it returns byte and errorMarshalJSON
itself?
Why not ToString()
or String()
or even GoString()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we return JSON :-) It's not meant to be human readable.
pkg/policy/rule.go
Outdated
} | ||
|
||
func mergeL4Port(r api.PortRule, p api.PortProtocol, proto string, resMap L4PolicyMap) int { | ||
fmt := fmt.Sprintf("%s:%d", proto, p.Port) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not %s/%d
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either is fine, it doesn't matter. Never exposed. Will change this if you look this better.
|
pkg/policy/api/rule.go, line 24 at r3 (raw file):
related to each? Comments from Reviewable |
e1c1792
to
97013e2
Compare
4ab023a
to
95c3c8b
Compare
pkg/policy/api/rule.go, line 44 at r4 (raw file):
typo: at egress. Comments from Reviewable |
95c3c8b
to
4346c2d
Compare
Fixed, thanks |
pkg/policy/api/rule.go, line 183 at r4 (raw file):
typo: list Comments from Reviewable |
pkg/policy/api/rule.go, line 193 at r4 (raw file):
/ie/is/ Comments from Reviewable |
4346c2d
to
6e3d8fd
Compare
// - All members of this structure are optional. If omitted or empty, the | ||
// member will have no effect on the rule. | ||
// - All members of this structure are evaluated independently, i.e. L4 ports | ||
// allowed with ToPorts do not depend on a match of the FromEndpoints in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please update the comment; There is no FromEndpoints in the EgressRule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
// | ||
// - All members of this structure are optional. If omitted or empty, the | ||
// member will have no effect on the rule. | ||
// - All members of this structure are evaluated independently, i.e. L4 ports |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"All members of this structure are evaluated independently". This would imply that as a user, I cannot enforce the following:
- Allow Ingress only from container labeled A (FromEndpoint=A) to port 80.
- Allow Ingress only from container labeled B (FromEndpoint=B) to port 8080.
B will be allowed to connect to port 80 in addition to port 8080. Similarly, A will be allowed to connect to port 8080 in addition to port 80. Right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. Two reasons:
- The BPF datapath is currently not capable of enforcing this. L3 endpoints are whitelisted via BPF map. The per endpoint L4 map is another BPF map. Referring to a 2nd BPF map from a 1st BPF map value was only just recently added so we will start supporting this with more recent kernels.
- k8s NetworkPolicy also defines this separate notion and we need to able to map k8s NetworkPolicy to our policy.
The idea is to add a field later on which introduces L4 policies which depend on a specific L3 rule. This could be done by adding a new ToPorts
inside FromEndpoints
or to add a flag to ToPorts
which allows to tie the ToPorts
to FromEndpoints
and FromCIDR
within the same IngressRule.
Signed-off-by: Thomas Graf <thomas@cilium.io>
- Replacement of tree structure with list of rules. Within the list, all rules enjoy the same priority - Separation of concerns will be addresses via a different concept introduced later. Users without an interest in separation of concern are not introduce to unnecessary complexity - Removal of deny rules. Require rules provide a great baseline and all use cases so far could be addressed with require rules without requiring implicit denies. - Removal of tree allows to drastically simplify label handling as labels are now always absolute. No need for a label to have a path. Signed-off-by: Thomas Graf <thomas@cilium.io>
Signed-off-by: Thomas Graf <thomas@cilium.io>
- Removes the {path} parameter from the API - Introduces search & delete by labels Signed-off-by: Thomas Graf <thomas@cilium.io>
Signed-off-by: Thomas Graf <thomas@cilium.io>
Signed-off-by: Thomas Graf <thomas@cilium.io>
Signed-off-by: Thomas Graf <thomas@cilium.io>
Fixes sporadic testsuite failures Signed-off-by: Thomas Graf <thomas@cilium.io>
Signed-off-by: Thomas Graf <thomas@cilium.io>
337a293
to
9a466b0
Compare
b6b5b63
to
42c977e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really awesome work @tgraf !
42c977e
to
ec6a666
Compare
Fixes: #674