Skip to content

Commit

Permalink
policy: implement DefaultAction: Pass
Browse files Browse the repository at this point in the history
This adjusts the policy enablement calculation to take in to account
Pass rules.

If an endpoint has a mix of Pass and Deny rules, then there is a
default-deny rule created, as expected. If an endpoint has only Pass
Allow rules, that is equivalent to an open policy, and no default-deny
rule is added. As a special case, if an endpoint has only Deny - Pass rules,
we need to synthesize a default-allow rule as well.

Signed-off-by: Casey Callendrello <cdc@isovalent.com>
  • Loading branch information
squeed committed Feb 1, 2024
1 parent a5016f0 commit f8ef6f3
Show file tree
Hide file tree
Showing 2 changed files with 355 additions and 51 deletions.
166 changes: 115 additions & 51 deletions pkg/policy/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,38 +603,6 @@ func (p *Repository) GetRulesMatching(lbls labels.LabelArray) (ingressMatch bool
return
}

// getMatchingRules returns whether any of the rules in a repository contain a
// rule with labels matching the given security identity, as well as
// a slice of all rules which match.
//
// Must be called with p.Mutex held
func (p *Repository) getMatchingRules(securityIdentity *identity.Identity) (
ingressMatch, egressMatch bool,
matchingRules ruleSlice) {

matchingRules = []*rule{}
for _, r := range p.rules {
isNode := securityIdentity.ID == identity.ReservedIdentityHost
selectsNode := r.NodeSelector.LabelSelector != nil
if selectsNode != isNode {
continue
}
if ruleMatches := r.matches(securityIdentity); ruleMatches {
// Don't need to update whether ingressMatch is true if it already
// has been determined to be true - allows us to not have to check
// lenth of slice.
if !ingressMatch {
ingressMatch = len(r.Ingress) > 0 || len(r.IngressDeny) > 0
}
if !egressMatch {
egressMatch = len(r.Egress) > 0 || len(r.EgressDeny) > 0
}
matchingRules = append(matchingRules, r)
}
}
return
}

// NumRules returns the amount of rules in the policy repository.
//
// Must be called with p.Mutex held
Expand Down Expand Up @@ -789,27 +757,123 @@ func (p *Repository) computePolicyEnforcementAndRules(securityIdentity *identity
if lbls.Has(labels.IDNameHost) && !option.Config.EnableHostFirewall {
return false, false, nil
}
switch GetPolicyEnabled() {
case option.AlwaysEnforce:
_, _, matchingRules = p.getMatchingRules(securityIdentity)
// If policy enforcement is enabled for the daemon, then it has to be
// enabled for the endpoint.

policyMode := GetPolicyEnabled()
// If policy enforcement isn't enabled, we do not enable policy
// enforcement for the endpoint. We don't care about returning any
// rules that match.
if policyMode == option.NeverEnforce {
return false, false, nil
}

matchingRules = []*rule{}
for _, r := range p.rules {
if r.matches(securityIdentity) {
matchingRules = append(matchingRules, r)
}
}

// If policy enforcement is enabled for the daemon, then it has to be
// enabled for the endpoint.
// If the endpoint has the reserved:init label, i.e. if it has not yet
// received any labels, always enforce policy (default deny).
if policyMode == option.AlwaysEnforce || lbls.Has(labels.IDNameInit) {
return true, true, matchingRules
case option.DefaultEnforcement:
ingress, egress, matchingRules = p.getMatchingRules(securityIdentity)
// If the endpoint has the reserved:init label, i.e. if it has not yet
// received any labels, always enforce policy (default deny).
if lbls.Has(labels.IDNameInit) {
return true, true, matchingRules
}

// Determine the default policy for each direction.
//
// By default, endpoints have no policy and all traffic is allowed.
// If any rules select the endpoint, then policy is enabled and non-allowed traffic
// is denied.
//
// Rules, however, can optionally be configured in "pass" mode, where they make
// no indication as to the default policy. If an endpoint only has "pass" rules,
// then there is no implicit default-deny rule and all traffic **that is not explicitly
// denied** is allowed.
//
// There are three possible cases _per direction_:
// 1: No rules are present, or only Allow + Pass rules. Then, policy is disabled.
// 2: A non-Pass rule is present. Then, policy is enabled and traffic is default-deny
// 3: Only Deny + Pass rules are present. Then, policy is enabled, but we must insert
// an additional allow-all rule. Since wildcard rules have lowest precedence
// in the datapath policy engine, this is a default-allow with specific deny
hasIngressDenyPass := false
hasEgressDenyPass := false
for _, r := range matchingRules {
if r.DefaultAction == api.PolicyActionPass {
// We need to account for the specific case of a Deny + Pass rule
if !hasIngressDenyPass && len(r.IngressDeny) > 0 {
hasIngressDenyPass = true
}

if !hasEgressDenyPass && len(r.EgressDeny) > 0 {
hasEgressDenyPass = true
}

// Note: we don't care about Allow + Pass rules; they cannot change
// the state of the endpoint.
} else {
// This is a default-Deny rule, so we must enable policy accordingly
if !ingress && (len(r.Ingress) > 0 || len(r.IngressDeny) > 0) {
ingress = true
}
if !egress && (len(r.Egress) > 0 || len(r.EgressDeny) > 0) {
egress = true
}
}
if ingress && egress {
break
}
}

// Default mode means that if rules contain labels that match this
// endpoint, then enable policy enforcement for this endpoint.
return ingress, egress, matchingRules
default:
// If policy enforcement isn't enabled, we do not enable policy
// enforcement for the endpoint. We don't care about returning any
// rules that match.
return false, false, nil
// If there are no default-Deny ingress rules, but there is a pass + deny ingress rule, we must
// add an additional wildcard rule for each direction
if !ingress && hasIngressDenyPass {
ingress = true
matchingRules = append(matchingRules, wildcardRule(securityIdentity.LabelArray, true))
}

// Same for egress -- synthesize a wildcard rule
if !egress && hasEgressDenyPass {
egress = true
matchingRules = append(matchingRules, wildcardRule(securityIdentity.LabelArray, false))
}

return
}

// wildcardRule generates a wildcard rule that only selects the given identity.
func wildcardRule(lbls labels.LabelArray, ingress bool) *rule {
r := &rule{
metadata: newRuleMetadata(),
}

if ingress {
r.Ingress = []api.IngressRule{
{
IngressCommonRule: api.IngressCommonRule{
FromEntities: []api.Entity{api.EntityAll},
},
},
}
} else {
r.Egress = []api.EgressRule{
{
EgressCommonRule: api.EgressCommonRule{
ToEntities: []api.Entity{api.EntityAll},
},
},
}
}

es := api.NewESFromLabels(lbls...)
if lbls.Has(labels.IDNameHost) {
r.NodeSelector = es
} else {
r.EndpointSelector = es
}
_ = r.Sanitize()

return r
}

0 comments on commit f8ef6f3

Please sign in to comment.