Skip to content

fix(apiserver): allow queryserver egress to Linseed via guardian on managed clusters - #5240

Merged
tianfeng92 merged 1 commit into
tigera:masterfrom
tianfeng92:CI-2048-apiserver-guardian-egress
Aug 24, 2026
Merged

fix(apiserver): allow queryserver egress to Linseed via guardian on managed clusters#5240
tianfeng92 merged 1 commit into
tigera:masterfrom
tianfeng92:CI-2048-apiserver-guardian-egress

Conversation

@tianfeng92

@tianfeng92 tianfeng92 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

A managed cluster runs no local Linseed, so the query server reaches it through guardian —
the URL LinseedEndpoint returns as LINSEED_URL. The calico-system.apiserver-access
policy permitted only the local Linseed pods, whose selector matches nothing on a managed
cluster, so the connection fell through to the policy's trailing Pass. The calico-system
tier's default-deny excludes calico-apiserver by design, so the traffic is then evaluated
against the customer's tiers, where any default-deny drops it.

The Manager policy board renders empty and the API call fails:

GET /api/v1/namespaces/calico-system/services/https:calico-api:8080/proxy/policies -> 500
Error: failed to get policy activity from linseed: error connecting linseed API:
Post "https://guardian.calico-system.svc/api/v1/policy_activity": context deadline exceeded

This branches the egress rule on the same condition LINSEED_URL already uses in this file:
render.GuardianEntityRule when a ManagementClusterConnection is present, local Linseed
otherwise.

The inert local-Linseed rule stays in the base policy in pkg/render/apiserver.go. It selects
no pods on a managed cluster, and APIServerConfiguration carries no cluster type to branch
on. Moving it into the extension would correct the layering at the cost of churning the base
fixture, which is better done on master alone than in a change that cherry-picks to four
release branches.

insertEgressBeforePass is extracted rather than duplicated. The OIDC path already used the
capacity-capped egress[:n-1:n-1] slice to avoid aliasing the backing array; both callers now
share it. Behaviour is unchanged.

Introduced in #4571, which added the dependency, hardcoded the guardian URL for managed
clusters, and shipped an egress rule covering only the self-hosted destination. #4786 later
refactored the URL onto the LinseedEndpoint helper and #4871 moved it into
pkg/enterprise/apiserver/; neither added the missing rule. First shipped in operator
v1.42.0, so release-v1.42 through release-v1.44 need cherry-picks.

Addresses CI-2048.

Testing

Verified end-to-end on a live Enterprise MCM managed cluster. A deny policy was applied in the
default tier selecting calico-apiserver, standing in for the customer's
deny-calico-system, and left in place for both runs. Only the operator image changed:

operator image guardian rule rendered GET /policies
original v1.45.0-0.dev-23-g40788b9b6bef no HTTP 500 after 19.5s
this change yes HTTP 200 in 0.58s, 13 policies, calico-system tier present

The 500 carried the reported error verbatim:

failed to get policy activity from linseed: error connecting linseed API:
Post "https://guardian.calico-system.svc/api/v1/policy_activity": context deadline exceeded

Also checked at the dataplane, with a probe pod carrying the k8s-app: calico-apiserver label:
TCP to guardian succeeded only with the fix, while a control destination the policy does not
permit stayed blocked in both runs — confirming the deny was genuinely in force rather than the
cluster being permissive.

Two things the cluster surfaced that the unit tests could not. The rendered rule is port 8080
while the query server dials https://guardian.calico-system.svc on 443; that is correct,
because the guardian Service is 443 → targetPort 8080 and Calico enforces egress against the
post-DNAT pod port. And guardian's own ingress policy already has an unrestricted
Allow ... ports=[8080] rule, so there is no ingress-side gap and this egress-only change is
sufficient.

Unit tests: new spec asserting the guardian rule is rendered ahead of the trailing Pass when
a ManagementClusterConnection is present, and a companion spec asserting its absence
otherwise. Reverting the production change while keeping the tests fails the managed-cluster
spec. pkg/enterprise/apiserver 51/51, pkg/controller/apiserver 28/28, pkg/render green.
make format-check clean, make static-checks reports 0 issues.

Backports: #5260 (release-v1.42), #5261 (release-v1.43), #5262 (release-v1.43-2),
#5263 (release-v1.44). release-v1.41 carries neither the egress rule nor LINSEED_URL, so
the affected range starts at release-v1.42 (CE v3.23.1).

Release Note

Fixed the Manager policy board rendering empty on managed clusters, where the query server was not permitted egress to Linseed through guardian.

For PR author

  • Tests for change.
  • If changing pkg/apis/, run make gen-files
  • If changing versions, run make gen-versions

For PR reviewers

A note for code reviewers - all pull requests must have the following:

  • Milestone set according to targeted release.
  • Appropriate labels:
    • kind/bug if this is a bugfix.
    • kind/enhancement if this is a a new feature.
    • enterprise if this PR applies to Calico Enterprise only.

🤖 Generated with Claude Code

…anaged clusters

A managed cluster runs no local Linseed, so the queryserver reaches it
through guardian. The apiserver-access policy only permitted the local
Linseed pods, which select nothing on a managed cluster, so the request
fell through to the trailing Pass and was denied by whatever the
customer's tiers enforce.

Branch the rule the same way LINSEED_URL is already branched in this
file: guardian when a ManagementClusterConnection is present, local
Linseed otherwise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@marvin-tigera marvin-tigera added this to the v1.45.0 milestone Aug 21, 2026
@tianfeng92
tianfeng92 marked this pull request as ready for review August 24, 2026 14:44
@tianfeng92
tianfeng92 requested a review from a team as a code owner August 24, 2026 14:44
Copilot AI lite review requested due to automatic review settings August 24, 2026 14:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Calico Enterprise API server extension to ensure the queryserver can reach Linseed on managed clusters by allowing egress to Linseed through Guardian, aligning network policy behavior with the LINSEED_URL endpoint logic.

Changes:

  • Insert a Guardian-targeted egress allow rule ahead of the trailing Pass rule when ManagementClusterConnection is present.
  • Extract a shared insertEgressBeforePass helper to avoid duplicating the slice-capacity-safe insertion logic.
  • Add unit tests asserting the Guardian egress rule is present (managed clusters) and absent (non-managed clusters).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
pkg/enterprise/apiserver/extension.go Adds managed-cluster Guardian egress rule insertion and factors out insertEgressBeforePass.
pkg/enterprise/apiserver/extension_test.go Adds specs verifying Guardian egress rule rendering behavior based on managed-cluster inputs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


// insertEgressBeforePass inserts rule ahead of the policy's trailing Pass rule, so that it is
// evaluated before the tier hands the traffic to subsequent tiers.
func insertEgressBeforePass(policy *v3.NetworkPolicy, rule v3.Rule) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty specific function that I'm not sure adds benefit over what was previously in lined. It's doing nothing special, just appending rules and there's not even any guarantee that the rule given here is a "Pass" rule, it's just a rule.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants