Skip to content
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

backport (v1.13): policy: Fix Deny Precedence Bug #25427

Merged
merged 3 commits into from
May 17, 2023

Conversation

nathanjsweet
Copy link
Member

  • Add Tests for Deny Precedence Bug:

    Currently, when a broad "Deny" policy is paired with a specific "Unmanaged CIDR" policy, then the "Unmanaged CIDR" policy will still be inserted into the policy map for an endpoint. This results in "Deny" policies not always taking precedence over "Allow" policies. This test confirms the bugs existence.

  • Fix Deny Precedence Bug:

    When the policy map state is created CIDRs are now checked against one another to ensure that deny-rules that supersede allow-rules when they should.

    DenyPreferredInsert has been refactored to use utility methods that make the complex boolean logic of policy precedence more atomic.

    Add NetsContainsAny method to pkg/ip to compare cases where one set of networks conatins or is equal to any network in another set.

  • endpoint: Add policy.Identity Implementation

    A policy.Identity implementation is necessary for the incremental update to the endpoint's policy map that can occur with L7 changes. Valid deny-policy entries may prohibit these L7 changes based on CIDR rules, which are only obtainable by looking up all potentially conflicting policies' labels. Thus l4.ToMapState needs access to the identity allocater to lookup "random" identity labels.

  • Additional commit from @joestringer to fix concurrent access to the SelectorCache, a bug the original work introduced.

  • Additional commit from @nathanjsweet to prevent a potential nil pointer dereference in the endpoint code.

policy: Promote Deny Policies from Beta to Stable

@nathanjsweet nathanjsweet added kind/bug This is a bug in the Cilium logic. release-note/major This PR introduces major new functionality to Cilium. kind/backports This PR provides functionality previously merged into master. backport/1.13 This PR represents a backport for Cilium 1.13.x of a PR that was merged to main. labels May 12, 2023
@nathanjsweet nathanjsweet requested a review from a team as a code owner May 12, 2023 18:49
@nathanjsweet
Copy link
Member Author

nathanjsweet commented May 12, 2023

/test-backport-1.13

Job 'Cilium-PR-K8s-1.24-kernel-4.19' hit: #25255 (94.53% similarity)

Job 'Cilium-PR-K8s-1.25-kernel-4.19' hit: #25255 (89.22% similarity)

@thorn3r
Copy link
Contributor

thorn3r commented May 16, 2023

/ci-e2e-1.13

@thorn3r
Copy link
Contributor

thorn3r commented May 16, 2023

k8s-1.24-kernel-4.19 hit a known flake: #25255
k8s-1.25-kernel-4.19 hit the same flake

nathanjsweet and others added 3 commits May 15, 2023 21:43
[ upstream commit c9f0def ]

- Add Tests for Deny Precedence Bug:

  Currently, when a broad "Deny" policy is paired with
  a specific "Unmanaged CIDR" policy, then the "Unmanaged
  CIDR" policy will still be inserted into the policy map
  for an endpoint. This results in "Deny" policies not
  always taking precedence over "Allow" policies. This
  test confirms the bugs existence.

- Fix Deny Precedence Bug:

  When the policy map state is created CIDRs
  are now checked against one another to ensure
  that deny-rules that supersede allow-rules
  when they should.

  `DenyPreferredInsert` has been refactored to
  use utility methods that make the complex boolean
  logic of policy precedence more atomic.

  Add `NetsContainsAny` method to `pkg/ip` to
  compare cases where one set of networks conatins
  or is equal to any network in another set.

- endpoint: Add policy.Identity Implementation

  A `policy.Identity` implementation is necessary
  for the incremental update to the endpoint's policy
  map that can occur with L7 changes. Valid deny-policy
  entries may prohibit these L7 changes based on CIDR
  rules, which are only obtainable by looking up all potentially
  conflicting policies' labels. Thus `l4.ToMapState` needs
  access to the identity allocater to lookup "random"
  identity labels.

Signed-off-by: Nate Sweet <nathanjsweet@pm.me>
[ upstream commit 52ace8e ]

Marco Iorio reports that with previous code, Cilium could crash at
runtime after importing a network policy, with the following error
printed to the logs:

    fatal error: concurrent map read and map write

The path for this issue is printed also in the logs, with the following
call stack:

    pkg/policy.(*SelectorCache).GetLabels(...)
    pkg/policy.(*MapStateEntry).getNets(...)
    pkg/policy.entryIdentityIsSupersetOf(...)
    pkg/policy.MapState.denyPreferredInsertWithChanges(...)
    pkg/policy.MapState.DenyPreferredInsert(...)
    pkg/policy.(*EndpointPolicy).computeDirectionL4PolicyMapEntries(...)
    pkg/policy.(*EndpointPolicy).computeDesiredL4PolicyMapEntries(...)
    pkg/policy.(*selectorPolicy).DistillPolicy(...)
    pkg/policy.(*cachedSelectorPolicy).Consume(...)
    pkg/endpoint.(*Endpoint).regeneratePolicy(...)
    ...

Upon further inspection, this call path is not grabbing the
SelectorCache lock at any point. If we check all of the incoming calls
to this function, we can see multiple higher level functions calling
into this function. The following tree starts from the deepest level of
the call stack and increasing indentation represents one level higher in
the call stack.

INCOMING CALLS
- f GetLabels github.com/cilium/cilium/pkg/policy • selectorcache.go
  - f getNets github.com/cilium/cilium/pkg/policy • mapstate.go
    - f entryIdentityIsSupersetOf github.com/cilium/cilium/pkg/policy • mapstate.go
      - f denyPreferredInsertWithChanges github.com/cilium/cilium/pkg/policy • mapstate.go
        - f DenyPreferredInsert github.com/cilium/cilium/pkg/policy • mapstate.go
          - f computeDirectionL4PolicyMapEntries github.com/cilium/cilium/pkg/policy • resolve.go
            - f computeDesiredL4PolicyMapEntries github.com/cilium/cilium/pkg/policy • resolve.go
              + f DistillPolicy github.com/cilium/cilium/pkg/policy • resolve.go <--- No SelectorCache lock
          - f DetermineAllowLocalhostIngress github.com/cilium/cilium/pkg/policy • mapstate.go
            + f DistillPolicy github.com/cilium/cilium/pkg/policy • resolve.go <--- No SelectorCache lock
        - f consumeMapChanges github.com/cilium/cilium/pkg/policy • mapstate.go
          + f ConsumeMapChanges github.com/cilium/cilium/pkg/policy • resolve.go <--- Already locks the SelectorCache

Read the above tree as "GetLabels() is called by getNets()",
"getNets() is called by entryIdentityIsSupersetOf()", and so on.
Siblings at the same level of indent represent alternate callers of the
function that is one level of indentation less in the tree, ie
DenyPreferredInsert() and consumeMapChanges() both call
denyPreferredInsertWithChanges().

As annotated above, we see that calls through DistillPolicy() do not
grab the SelectorCache lock. Given that ConsumeMapChanges() grabs the
SelectorCache lock, we cannot introduce a new lock acquisition in any
descendent function, otherwise it would introduce a deadlock in
goroutines that follow that call path. This provides us the option to
lock at some point from the sibling of consumeMapChanges() or higher in
the call stack.

Given that the ancestors of DenyPreferredInsert() are all from
DistillPolicy(), we can amortize the cost of grabbing the SelectorCache
lock by grabbing it once for the policy distillation phase rather than
putting the lock into DenyPreferredInsert() where the SelectorCache
could be locked and unlocked for each map state entry.

Future work could investigate whether these call paths could make use of
the IdentityAllocator's cache of local identities for the GetLabels()
call rather than relying on the SelectorCache, but for now this patch
should address the immediate locking issue that triggers agent crashes.

CC: Nate Sweet <nathanjsweet@pm.me>
Fixes: c9f0def ("policy: Fix Deny Precedence Bug")
Reported-by: Marco Iorio <marco.iorio@isovalent.com>
Co-authored-by: Chris Tarazi <chris@isovalent.com>
Signed-off-by: Joe Stringer <joe@cilium.io>
Signed-off-by: Nate Sweet <nathanjsweet@pm.me>
[ upstream commit bfbe5a2 ]

The policyIdentityLabelLookup wrapper for Endpoint implements
the GetLabels interface method. This is necessary for the
constructing the MapState of the policy engine. This implementation
incorrectly did not check if the identity returned by LookupIdentityByID
was nil. This fixes this bug, which heretofore has not caused any issues.

Signed-off-by: Nate Sweet <nathanjsweet@pm.me>
@thorn3r thorn3r force-pushed the pr/nathanjsweet/backport-fix-deny-precedence-bug branch from 1e929aa to 08f4af6 Compare May 16, 2023 01:44
@thorn3r
Copy link
Contributor

thorn3r commented May 16, 2023

rebased onto latest v1.13 to apply fix for ci-e2e-1.13

@thorn3r
Copy link
Contributor

thorn3r commented May 16, 2023

/test-backport-1.13

@thorn3r
Copy link
Contributor

thorn3r commented May 16, 2023

/ci-e2e-1.13

@thorn3r
Copy link
Contributor

thorn3r commented May 16, 2023

/ci-aks-1.13

@thorn3r
Copy link
Contributor

thorn3r commented May 16, 2023

ConformanceAKS - test passed but cleanup failed: #20887
k8s-1.20-kernel-4.19 - related to #25483

Cannot initiate the connection to us.archive.ubuntu.com:80 (2001:67c:1562::15). - connect (101: Network is unreachable)

k8s-1.24-kernel-4.19 same as above
k8s-1.25-kernel-4.19 same as above

lots of flakiness from archive.ubuntu.com today

@nathanjsweet nathanjsweet changed the title backport: policy: Fix Deny Precedence Bug backport (v1.13): policy: Fix Deny Precedence Bug May 16, 2023
@aditighag
Copy link
Member

Thanks @thorn3r for documenting the test failures.

lots of flakiness from archive.ubuntu.com today

Given that 3 Jenkins jobs didn't even start, I would like a confirmation from the author of the backports PR @nathanjsweet whether it's okay to ignore the failures. There are multiple Jenkins jobs that test different combinations of k8s and kernel versions, and some of the jobs ran successfully, but I don't have full context for the PR changes to make a judgement call around test coverage.

@nathanjsweet
Copy link
Member Author

This is mergeable. This PR cannot be affected by K8s version or Linux version changes.

@nathanjsweet nathanjsweet added the ready-to-merge This PR has passed all tests and received consensus from code owners to merge. label May 17, 2023
@aditighag aditighag merged commit 37db735 into v1.13 May 17, 2023
58 of 62 checks passed
@aditighag aditighag deleted the pr/nathanjsweet/backport-fix-deny-precedence-bug branch May 17, 2023 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport/1.13 This PR represents a backport for Cilium 1.13.x of a PR that was merged to main. kind/backports This PR provides functionality previously merged into master. kind/bug This is a bug in the Cilium logic. ready-to-merge This PR has passed all tests and received consensus from code owners to merge. release-note/major This PR introduces major new functionality to Cilium.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants