Skip to content

CFP: Clarify the intent for policies with default deny specified with no rules #35558

Description

@joestringer

Problem

As of Cilium v1.16, in order to apply a default deny policy, a CNP or CCNP must have at least one rule in an egress, egressDeny, ingress or ingressDeny stanza.

Furthermore, in Cilium v1.16 there is a new enableDefaultDeny field which seems like it should be able to be used to enforce a default deny posture without listing any rules in one of the rule allow/deny fields. If this field is specified and describes that default deny should be enabled, but there are no rules, then the network policy does not enforce a default deny.

Proposal A

This CFP proposes that the following network policy should enable a default deny posture even though it does not contain an egress, egressDeny, ingress or ingressDeny field.

apiVersion: cilium.io/v2                                                                                                                                                             
kind: CiliumClusterwideNetworkPolicy                                                                                                                                                 
metadata:                                                                                                                                                                            
  name: default-deny                                                                                                                                                                 
spec:                                                                                                                                                                                
  endpointSelector:                                                                                                                                                                  
    matchExpressions:                                                                                                                                                                
      - key: "io.kubernetes.pod.namespace"                                                                                                                                           
        operator: "NotIn"                                                                                                                                                            
        values:                                                                                                                                                                      
        - "kube-system"                                                                                                                                                              
  enableDefaultDeny:                                                                                                                                                                 
    egress: true                                                                                                                                                                     
    ingress: true

EDIT: Note that there are counter-proposals for different behavior in the discussion below.

Activity

  1. added
    kind/cfpCilium Feature Proposal
    kind/enhancementThis would improve or streamline existing functionality.
    sig/policyImpacts whether traffic is allowed or denied based on user-defined policies.
    on Oct 25, 2024
  2. joestringer commented on Oct 25, 2024

    @joestringer
    MemberAuthor

    I believe that currently this is considered to be an invalid policy as it is not fully formed (does not contain an egress, egressDeny, ingress or ingressDeny field). Though I will also note that the cilium operator's network policy validator does not explicitly identify the network policy above as invalid, it just does not have any effect.

  3. squeed commented on Oct 28, 2024

    @squeed
    Contributor

    My instinct would be to reject empty policies at the apiserver. There should probably be a OneOf directive for Egress / EgressDeny / Ingress / IngressDeny stanzas.

    One of the trickier things to understand in the Kubernetes style of network policy is the effect of empty fields. Does it select everything or nothing? Will setting this field change some sort of hidden local variable?

    So, I'm generally in favor of preventing "should be a no-op" entry, much how Linux usually rejects unknown flags rather than ignoring them.

  4. joestringer commented on Oct 29, 2024

    @joestringer
    MemberAuthor

    That's certainly also an option. Personally when I read the YAML snippet posted in the issue description, the intent seems crystal clear to me.. though admittedly it could bring up some other semantic questions that are less obvious.

    For instance, if we allowed there to be no ingress/egress stanzas, would that mean that the following also valid CNP? What does it mean?

    apiVersion: cilium.io/v2                                                                                                                                                             
    kind: CiliumClusterwideNetworkPolicy                                                                                                                                                 
    metadata:                                                                                                                                                                            
      name: default-deny                                                                                                                                                                 
    spec:                                                                                                                                                                                
      endpointSelector:                                                                                                                                                                  
        matchExpressions:                                                                                                                                                                
          - key: "io.kubernetes.pod.namespace"                                                                                                                                           
            operator: "NotIn"                                                                                                                                                            
            values:                                                                                                                                                                      
            - "kube-system"                                                                                                                                                              
      enableDefaultDeny: {}
    

    I would interpret this one to be a complete no-op, and therefore I think it'd be helpful to reject this policy. Maybe that's speaking a bit to your instinct @squeed about requiring one of the Egress / EgressDeny / Ingress / IngressDeny stanzas.

  5. renyunkang commented on Nov 8, 2024

    @renyunkang
    Contributor

    If the enableDefaultDeny field in a Cilium Network Policy (CNP) is set to {} (an empty object), two solutions are possible:

    1. Strict validation with status updates
      Treat an empty enableDefaultDeny as an invalid configuration. Reject the policy application, update the CNP status to reflect the validation failure, and provide a clear error message indicating the missing ingress and egress settings.

    2. Defaulting to ingress: false, egress: false.
      This means that default-deny behavior is disabled for both ingress and egress traffic. The policy would be applied without error, providing a smoother user experience.

    While both are viable, I personally favor Option 2, defaulting to disabling default-deny for both ingress and egress.

  6. joestringer commented on Nov 8, 2024

    @joestringer
    MemberAuthor

    @renyunkang are you considering the state of the enableDefaultDeny: {} field with or without rule statements like ingress, egress, etc?

    For existing policies written without the enableDefaultDeny field, the behavior must not change. This is equivalent to installing the resource without the enableDefaultDeny field, and then if you retrieve the resource with the new schema, it translates the field to the empty value, enableDefaultDeny: {}. Since the existing policies would enable a default deny when there is at least one rule in an ingress, egress, etc stanza, this means that the enableDefaultDeny: {} field must not disable default deny for each direction. In order to disable default deny, you must explicitly set enableDefaultDeny: ingress: false (and/or equivalent egress statement).

    As a general constraint of the system, if some configuration has no effect, then I think it can improve user experience for the resource to be rejected. For example the resource described here. This helps to communicate when the user has created a configuration that does not do what they intend to do.

  7. renyunkang commented on Nov 11, 2024

    @renyunkang
    Contributor

    I've reconsidered; please review the following description and let me know if it's accurate. @joestringer

    If the daemon has EnableNonDefaultDenyPolicies set to true.

    • One of the following must be configured: Ingress, IngressDeny, Egress, EgressDeny, EnableDefaultDeny.Ingress, or EnableDefaultDeny.Egress. Otherwise, the policy configuration is considered erroneous.

    • For CNP with Ingress or IngressDeny rules, if EnableDefaultDeny.Ingress is specified, its value is used; otherwise, it defaults to true. The same logic applies to Egress and EnableDefaultDeny.Egress.

    If the daemon has EnableNonDefaultDenyPolicies set to false, the previous logic is used.

    • One of Ingress, IngressDeny, Egress, or EgressDeny must be configured; otherwise, the policy configuration is considered erroneous. EnableDefaultDeny defaults to {ingress: true, egress: true}.
  8. joestringer commented on Nov 20, 2024

    @joestringer
    MemberAuthor

    To align with @squeed's statements above, I think it would be simpler to just require a rule to be specified. Effectively that means there would be a small change from your post above @renyunkang :

    If the daemon has EnableNonDefaultDenyPolicies set to true:

    • One of the following must be configured: Ingress, IngressDeny, Egress, EgressDeny, EnableDefaultDeny.Ingress, or EnableDefaultDeny.Egress.

    One of the benefits is that for this simpler expression it should be easier to embed this in the CRD validation so that the kube-apiserver enforces it, rather than having the cilium-agent enforce it after the resource is already configured. Better UX.

  9. renyunkang commented on Nov 26, 2024

    @renyunkang
    Contributor

    To align with @squeed's statements above, I think it would be simpler to just require a rule to be specified. Effectively that means there would be a small change from your post above @renyunkang :

    If the daemon has EnableNonDefaultDenyPolicies set to true:

    • One of the following must be configured: Ingress, IngressDeny, Egress, EgressDeny, EnableDefaultDeny.Ingress, or EnableDefaultDeny.Egress.

    One of the benefits is that for this simpler expression it should be easier to embed this in the CRD validation so that the kube-apiserver enforces it, rather than having the cilium-agent enforce it after the resource is already configured. Better UX.

    The current description completely contradicts the CFP title. Is this modification consistent with the CFP proposal? Or should we revise the CFP proposal title?

  10. renyunkang commented on Nov 27, 2024

    @renyunkang
    Contributor

    With the following rule configured, what happens to ingress traffic?

    apiVersion: cilium.io/v2
    kind: CiliumNetworkPolicy
    metadata:
      name: default-deny
    spec:
      endpointSelector:
        matchExpressions:
          - key: "io.kubernetes.pod.namespace"
            operator: "NotIn"
            values:
            - "kube-system"
      egress:
      - toFQDNs:
        - matchName: "api.github.com"
      - toEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": kube-system
            "k8s:k8s-app": kube-dns
        toPorts:
        - ports:
          - port: "53"
            protocol: ANY
          rules:
            dns:
            - matchPattern: "*"
      enableDefaultDeny:
        egress: true
        ingress: true
    apiVersion: cilium.io/v2
    kind: CiliumNetworkPolicy
    metadata:
      name: default-deny
    spec:
      endpointSelector:
        matchExpressions:
          - key: "io.kubernetes.pod.namespace"
            operator: "NotIn"
            values:
            - "kube-system"
      egress:
      - toFQDNs:
        - matchName: "api.github.com"
      - toEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": kube-system
            "k8s:k8s-app": kube-dns
        toPorts:
        - ports:
          - port: "53"
            protocol: ANY
          rules:
            dns:
            - matchPattern: "*"
      enableDefaultDeny:
        egress: true
        ingress: false

    Will setting enableDefaultDeny.ingress to true or false allow all ingress traffic in either case? @joestringer @squeed

  11. joestringer commented on Nov 27, 2024

    @joestringer
    MemberAuthor

    The current description completely contradicts the CFP title. Is this modification consistent with the CFP proposal? Or should we revise the CFP proposal title?

    Sorry, I agree it contradicts the CFP title. I created the proposal to try to gather feedback but I don't hold a strong opinion that my original description of the solution is the right way. Probably if I were to rewrite the issue, I would reframe it to first describe:

    • The problem: it's confusing that enableDefaultDeny.egress/enableDefaultDeny.ingress by itself has no effect
    • A solution: my initial proposal was to change the semantics so that it takes effect.
    • A different solution: @squeed's counter-proposal was that we just declare such policies to be invalid.

    Your questions are super helpful to help us reason about what we think the right solution to the problem is. I think that the logical conclusion of Casey's proposal would be that the network policies you proposed in your latest post should both be rejected, because they configure the ingress default deny posture but they do not contain any ingress rules.

  12. changed the title [-]CFP: Support default deny policy with no rules[/-] [+]CFP: Clarify the intent for policies with default deny specified with no rules[/+] on Nov 27, 2024
  13. renyunkang commented on Nov 28, 2024

    @renyunkang
    Contributor

    Okay, based on your description, let's elaborate with an example: @joestringer @squeed

    The problem:

    Before v1.16.0, the following rule allowed all ingress traffic:

    apiVersion: cilium.io/v2
    kind: CiliumNetworkPolicy
    metadata:
      name: default-deny
    spec:
      endpointSelector:
        matchExpressions:
        - key: "io.kubernetes.pod.namespace"
          operator: "NotIn"
          values:
          - "kube-system"
      egress:
      - toFQDNs:
        - matchName: "api.github.com"
      - toEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": kube-system
            "k8s:k8s-app": kube-dns
        toPorts:
        - ports:
          - port: "53"
            protocol: ANY
          rules:
            dns:
            - matchPattern: "*"

    After v1.16.0 introduced enableDefaultDeny, the above configuration, regardless of whether enableDefaultDeny.ingress is true or false, still allows all ingress traffic. This is confusing for users because the configuration has no effect.

    Proposed Solutions:

    Solution 1: Change the semantics of enableDefaultDeny so that it takes effect as intended.
    Solution 2: Declare the policy invalid.

    My Understanding:

    Solution 1 is easy to understand.
    How should Solution 2 be interpreted? Detecting invalid policies works well for policies like this:

    apiVersion: cilium.io/v2
    kind: CiliumNetworkPolicy
    metadata:
      name: default-deny
    spec:
      endpointSelector:
        matchExpressions:
        - key: "io.kubernetes.pod.namespace"
          operator: "NotIn"
          values:
          - "kube-system"
      enableDefaultDeny:
        egress: true
        ingress: true

    However, Solution 2 might not be suitable for the first example. Should we still use Solution 2 for the first example? Since there's no ingress or ingressDeny section, enableDefaultDeny.ingress cannot be configured, right?

    Your questions are super helpful to help us reason about what we think the right solution to the problem is. I think that the logical conclusion of Casey's proposal would be that the network policies you proposed in #35558 (comment) should both be rejected, because they configure the ingress default deny posture but they do not contain any ingress rules.

    For existing policies written without the enableDefaultDeny field, the behavior must not change.

    Furthermore, if setting enableDefaultDeny.ingress to either true or false results in the rejection of all ingress traffic, will the behavior change for policies without the enableDefaultDeny field after users upgrade from v1.15.x or lower versions?

  14. joestringer commented on Dec 3, 2024

    @joestringer
    MemberAuthor

    It's a bit nuanced to avoid severely breaking existing clusters. We can think of the default deny configuration as a tri-state for each direction: It is either not present in the policy (v1.15-compatible), or it is present with a true (deny all by default) or false (do not deny all by default; only apply the rules explicitly listed in the policy) value.

    This is how I think that Solution 2 solves these problems:

    If there is an ingress or ingressDeny stanza with at least one rule:

    1. enableDefaultDeny.ingress: true or no enableDefaultDeny section must place the subject endpoints into default deny, ie reject all ingress traffic that does not match the rules.
    2. enableDefaultDeny.ingress: false must not place the subject endpoints into default deny. Only the traffic clearly selected by the rules will be impacted by the rule.

    If there is no ingress or ingressDeny stanza:

    1. If there is enableDefaultDeny.ingress with any value: Reject the policy. No change in enforcement, this should ideally occur when the resource is created or at worst case cilium-agent rejects the policy.
    2. If there is no enableDefaultDeny.ingress or enableDefaultDeny stanza: no change.

    If there is no enableDefaultDeny field:

    1. No change vs. v1.15 policy enforcement or network policy parsing.

    Then the equivalent should apply for egress / egressDeny with enableDefaultDeny.egress.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

kind/cfpCilium Feature Proposalkind/enhancementThis would improve or streamline existing functionality.sig/policyImpacts whether traffic is allowed or denied based on user-defined policies.

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions