Skip to content

CMP-4569: Create operand NetworkPolicies at runtime - #1364

Open
abushkin-redhat wants to merge 3 commits into
ComplianceAsCode:masterfrom
abushkin-redhat:CMP-4569
Open

CMP-4569: Create operand NetworkPolicies at runtime#1364
abushkin-redhat wants to merge 3 commits into
ComplianceAsCode:masterfrom
abushkin-redhat:CMP-4569

Conversation

@abushkin-redhat

Copy link
Copy Markdown
Collaborator

CMP-4569: Create operand NetworkPolicies at runtime

Depends on #1313 (CMP-4496). This PR consumes the networking.k8s.io/networkpolicies
RBAC (create/delete/get/update) added there. Until #1313 merges, this PR's diff
includes those two RBAC commits as its base; once #1313 merges they drop out on rebase.

What

Per HPSTRAT-104, layered operators must create NetworkPolicies for their operand pods.
This is the operand half of that work for the Compliance Operator (the RBAC half is #1313).
The operator now creates and reconciles label-scoped NetworkPolicies for all of its
operand pods, so operand traffic is governed at runtime instead of being unrestricted.

  • Add a common marker label compliance.openshift.io/netpol-managed: "" to every operand
    pod template: node scanner, platform scanner, aggregator, result server, profileparser,
    and the suite rerunner CronJob.
    • For the two operand Deployments (result server, profileparser) the marker is added
      to the pod template only, never to the Deployment/Service selector, since a
      Deployment's spec.selector is immutable after creation.
  • Add three named NetworkPolicies in the operator namespace, reconciled by the
    ComplianceScan controller (pkg/controller/compliancescan/networkpolicy.go):
    1. compliance-operator-operands-default-deny — selects operand pods; denies all ingress
      and egress (baseline).
    2. compliance-operator-operands-allow-egress — allows all egress from operand pods.
    3. compliance-operator-resultserver-allow-ingress — allows ingress to the result server
      on 8443/TCP from pod-network scanners (workload=scanner podSelector) and from
      host-networked node scanners (the OVN policy-group.network.openshift.io/host-network
      namespaceSelector).
  • Reconcile the policies at the start of the scan launching phase, before any operand
    pod is created, so operands start with their network access already governed. Reconcile
    is get-then-create/update (idempotent), with no List or Watch — matching the RBAC
    granted in CMP-4496: Add NetworkPolicy RBAC for operands to operator Role and CSV #1313.
  • Read NetworkPolicy directly from the API server (manager client
    Cache.DisableFor: NetworkPolicy); see Cache bypass below.
  • Centralize the workload operand label key/values as constants (config.go) so the
    policy selectors stay in sync with the pod templates.
  • Regenerate coverage-baseline.txt (the new tests raise coverage; make build/test-unit
    gates on the baseline).

Why

Operand pods (scanners, result server, aggregator, profileparser, rerunner) previously ran
with no NetworkPolicy, so all ingress/egress was allowed. HPSTRAT-104 requires operators to
manage NetworkPolicies for their operands dynamically at runtime (not via the OLM bundle
manifest). This PR provides that: a default-deny baseline plus the minimal allow rules the
operands actually need.

Cache bypass (why NetworkPolicy is read uncached)

controller-runtime's default client is cache-backed: a Get for a type the cache has not
seen lazily starts an informer, which performs a list + watch. The operand RBAC
(#1313) intentionally grants only get/create/update/delete — not list/watch.
Without intervention, the first Get of a NetworkPolicy starts an informer that fails with
networkpolicies ... is forbidden: cannot list, the reconcile errors, and scans hang in
LAUNCHING. The manager is therefore configured with:

Client: client.Options{Cache: &client.CacheOptions{
    DisableFor: []client.Object{&networkingv1.NetworkPolicy{}},
}}

so NetworkPolicy reads go straight to the API server (get only; writes already bypass the
cache). This keeps the RBAC least-privilege (no list/watch) and requires no informer.

Behavior notes (intended, worth knowing)

  • Scan launch is now fail-closed on NetworkPolicy reconciliation. If policy reconcile
    fails (e.g., an admission/validating webhook rejects a NetworkPolicy, an API error, or a
    networkpolicies quota), the scan stays in LAUNCHING and requeues rather than launching
    operands ungoverned. Previously the scan path had no dependency on NetworkPolicy.
  • Policies are created lazily and self-heal only on scan launch. They are created the
    first time a scan enters LAUNCHING. There is no controller Watch/Owns on
    NetworkPolicy (by design — no list/watch RBAC), so out-of-band deletion or edits are
    re-asserted on the next scan launch, not continuously.
  • Reconcile runs per scan. The three policies are namespace-global but re-asserted on
    every scan's launching phase (idempotent; a few extra GETs).
  • Egress is intentionally allow-all. The API server is not selectable by NetworkPolicy,
    and scanners make arbitrary external egress (oscap --fetch-remote-resources unless
    NoExternalResources). With a single operand bucket, the shared egress policy must
    accommodate the scanner, so egress is open; the enforced hardening here is on ingress.
  • New label on operand pods. All operand pods now carry
    compliance.openshift.io/netpol-managed="".
  • NetworkPolicy is read uncached operator-wide (see above); no other operator code
    reads NetworkPolicy via the manager client, and the CEL/api-resource-collector content
    path is a separate binary/ServiceAccount and is unaffected.

Known limitations / follow-ups

  • profileparser marker is applied on next redeploy after upgrade. The profileparser
    Deployment is long-lived and workloadNeedsUpdate compares images/commands, not labels,
    so on operator upgrade an existing profileparser Deployment is not recreated solely to
    gain the marker — it stays outside default-deny (i.e., today's allow-all behavior) until a
    content/image change triggers a redeploy. The per-scan/per-suite operands (scanners,
    result server, aggregator, rerunner) pick up the marker on the next scan.
  • Drift comparison uses reflect.DeepEqual on .Spec. If the API server ever defaults
    a NetworkPolicy field we don't set identically, reconcile could issue a redundant Update
    each pass. All spec fields we care about are set explicitly and no such churn was observed
    on-cluster; a follow-up could narrow the comparison to owned fields if it ever appears.
  • Result server ingress is scanner + host-network only. Any future consumer that reaches
    the result server over the network on 8443 (rather than via the results PVC) would be
    denied. oc compliance fetch-raw reads the PVC via an extractor pod, so it is unaffected.

Test plan

Unit (local; in-memory fake client, no cluster/envtest)

make test-unit
# or the targeted packages:
go test ./pkg/controller/compliancescan/... ./pkg/controller/profilebundle/... ./pkg/controller/compliancesuite/...

Covers: policy builders (selectors, policy types, allow-all-egress shape, result-server
ingress port 8443 and both from peers); reconcileNetworkPolicies create / idempotent /
drift-correction (get/create/update only); and that every operand pod template carries the
marker while the result-server and profileparser Deployment selectors do not.

E2E (requires OCP on OVN-Kubernetes)

tests/e2e/deployment/networkpolicy_test.goTestOperandNetworkPoliciesAreReconciled:
runs a node ComplianceScan (host-networked scanner → result server on 8443, the critical
ingress path), waits for DONE, then asserts all three policies exist, default-deny selects
operands with no allow rules, and the result-server ingress opens 8443.

export TEST_OPERATOR_NAMESPACE=openshift-compliance
make image-to-cluster e2e-deployment \
  E2E_GO_TEST_FLAGS="-v -run TestOperandNetworkPoliciesAreReconciled -test.timeout 45m"

Verified on ClusterBot openshift-e2e-aws 4.22 (RHEL-10, TechPreviewNoUpgrade, OVN):
the scan progressed RUNNING → AGGREGATING → DONE with policies in place, and all
assertions passed (--- PASS: TestOperandNetworkPoliciesAreReconciled).

Optional manual confirmation:

oc get networkpolicy -n openshift-compliance
# compliance-operator-operands-default-deny
# compliance-operator-operands-allow-egress
# compliance-operator-resultserver-allow-ingress

@openshift-ci-robot

openshift-ci-robot commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

@abushkin-redhat: This pull request references CMP-4569 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.1.0" version, but no target version was set.

Details

In response to this:

CMP-4569: Create operand NetworkPolicies at runtime

Depends on #1313 (CMP-4496). This PR consumes the networking.k8s.io/networkpolicies
RBAC (create/delete/get/update) added there. Until #1313 merges, this PR's diff
includes those two RBAC commits as its base; once #1313 merges they drop out on rebase.

What

Per HPSTRAT-104, layered operators must create NetworkPolicies for their operand pods.
This is the operand half of that work for the Compliance Operator (the RBAC half is #1313).
The operator now creates and reconciles label-scoped NetworkPolicies for all of its
operand pods, so operand traffic is governed at runtime instead of being unrestricted.

  • Add a common marker label compliance.openshift.io/netpol-managed: "" to every operand
    pod template: node scanner, platform scanner, aggregator, result server, profileparser,
    and the suite rerunner CronJob.
  • For the two operand Deployments (result server, profileparser) the marker is added
    to the pod template only, never to the Deployment/Service selector, since a
    Deployment's spec.selector is immutable after creation.
  • Add three named NetworkPolicies in the operator namespace, reconciled by the
    ComplianceScan controller (pkg/controller/compliancescan/networkpolicy.go):
  1. compliance-operator-operands-default-deny — selects operand pods; denies all ingress
    and egress (baseline).
  2. compliance-operator-operands-allow-egress — allows all egress from operand pods.
  3. compliance-operator-resultserver-allow-ingress — allows ingress to the result server
    on 8443/TCP from pod-network scanners (workload=scanner podSelector) and from
    host-networked node scanners (the OVN policy-group.network.openshift.io/host-network
    namespaceSelector).
  • Reconcile the policies at the start of the scan launching phase, before any operand
    pod is created, so operands start with their network access already governed. Reconcile
    is get-then-create/update (idempotent), with no List or Watch — matching the RBAC
    granted in CMP-4496: Add NetworkPolicy RBAC for operands to operator Role and CSV #1313.
  • Read NetworkPolicy directly from the API server (manager client
    Cache.DisableFor: NetworkPolicy); see Cache bypass below.
  • Centralize the workload operand label key/values as constants (config.go) so the
    policy selectors stay in sync with the pod templates.
  • Regenerate coverage-baseline.txt (the new tests raise coverage; make build/test-unit
    gates on the baseline).

Why

Operand pods (scanners, result server, aggregator, profileparser, rerunner) previously ran
with no NetworkPolicy, so all ingress/egress was allowed. HPSTRAT-104 requires operators to
manage NetworkPolicies for their operands dynamically at runtime (not via the OLM bundle
manifest). This PR provides that: a default-deny baseline plus the minimal allow rules the
operands actually need.

Cache bypass (why NetworkPolicy is read uncached)

controller-runtime's default client is cache-backed: a Get for a type the cache has not
seen lazily starts an informer, which performs a list + watch. The operand RBAC
(#1313) intentionally grants only get/create/update/delete — not list/watch.
Without intervention, the first Get of a NetworkPolicy starts an informer that fails with
networkpolicies ... is forbidden: cannot list, the reconcile errors, and scans hang in
LAUNCHING. The manager is therefore configured with:

Client: client.Options{Cache: &client.CacheOptions{
   DisableFor: []client.Object{&networkingv1.NetworkPolicy{}},
}}

so NetworkPolicy reads go straight to the API server (get only; writes already bypass the
cache). This keeps the RBAC least-privilege (no list/watch) and requires no informer.

Behavior notes (intended, worth knowing)

  • Scan launch is now fail-closed on NetworkPolicy reconciliation. If policy reconcile
    fails (e.g., an admission/validating webhook rejects a NetworkPolicy, an API error, or a
    networkpolicies quota), the scan stays in LAUNCHING and requeues rather than launching
    operands ungoverned. Previously the scan path had no dependency on NetworkPolicy.
  • Policies are created lazily and self-heal only on scan launch. They are created the
    first time a scan enters LAUNCHING. There is no controller Watch/Owns on
    NetworkPolicy (by design — no list/watch RBAC), so out-of-band deletion or edits are
    re-asserted on the next scan launch, not continuously.
  • Reconcile runs per scan. The three policies are namespace-global but re-asserted on
    every scan's launching phase (idempotent; a few extra GETs).
  • Egress is intentionally allow-all. The API server is not selectable by NetworkPolicy,
    and scanners make arbitrary external egress (oscap --fetch-remote-resources unless
    NoExternalResources). With a single operand bucket, the shared egress policy must
    accommodate the scanner, so egress is open; the enforced hardening here is on ingress.
  • New label on operand pods. All operand pods now carry
    compliance.openshift.io/netpol-managed="".
  • NetworkPolicy is read uncached operator-wide (see above); no other operator code
    reads NetworkPolicy via the manager client, and the CEL/api-resource-collector content
    path is a separate binary/ServiceAccount and is unaffected.

Known limitations / follow-ups

  • profileparser marker is applied on next redeploy after upgrade. The profileparser
    Deployment is long-lived and workloadNeedsUpdate compares images/commands, not labels,
    so on operator upgrade an existing profileparser Deployment is not recreated solely to
    gain the marker — it stays outside default-deny (i.e., today's allow-all behavior) until a
    content/image change triggers a redeploy. The per-scan/per-suite operands (scanners,
    result server, aggregator, rerunner) pick up the marker on the next scan.
  • Drift comparison uses reflect.DeepEqual on .Spec. If the API server ever defaults
    a NetworkPolicy field we don't set identically, reconcile could issue a redundant Update
    each pass. All spec fields we care about are set explicitly and no such churn was observed
    on-cluster; a follow-up could narrow the comparison to owned fields if it ever appears.
  • Result server ingress is scanner + host-network only. Any future consumer that reaches
    the result server over the network on 8443 (rather than via the results PVC) would be
    denied. oc compliance fetch-raw reads the PVC via an extractor pod, so it is unaffected.

Test plan

Unit (local; in-memory fake client, no cluster/envtest)

make test-unit
# or the targeted packages:
go test ./pkg/controller/compliancescan/... ./pkg/controller/profilebundle/... ./pkg/controller/compliancesuite/...

Covers: policy builders (selectors, policy types, allow-all-egress shape, result-server
ingress port 8443 and both from peers); reconcileNetworkPolicies create / idempotent /
drift-correction (get/create/update only); and that every operand pod template carries the
marker while the result-server and profileparser Deployment selectors do not.

E2E (requires OCP on OVN-Kubernetes)

tests/e2e/deployment/networkpolicy_test.goTestOperandNetworkPoliciesAreReconciled:
runs a node ComplianceScan (host-networked scanner → result server on 8443, the critical
ingress path), waits for DONE, then asserts all three policies exist, default-deny selects
operands with no allow rules, and the result-server ingress opens 8443.

export TEST_OPERATOR_NAMESPACE=openshift-compliance
make image-to-cluster e2e-deployment \
 E2E_GO_TEST_FLAGS="-v -run TestOperandNetworkPoliciesAreReconciled -test.timeout 45m"

Verified on ClusterBot openshift-e2e-aws 4.22 (RHEL-10, TechPreviewNoUpgrade, OVN):
the scan progressed RUNNING → AGGREGATING → DONE with policies in place, and all
assertions passed (--- PASS: TestOperandNetworkPoliciesAreReconciled).

Optional manual confirmation:

oc get networkpolicy -n openshift-compliance
# compliance-operator-operands-default-deny
# compliance-operator-operands-allow-egress
# compliance-operator-resultserver-allow-ingress

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@github-actions

Copy link
Copy Markdown

🤖 To deploy this PR, run the following command:

make catalog-deploy CATALOG_IMG=ghcr.io/complianceascode/compliance-operator-catalog:1364-4237b3afe353659da30a0df5c7999ec9ab01710a

@Vincent056 Vincent056 added this to the 1.10.0 milestone Aug 19, 2026
taimurhafeez
taimurhafeez previously approved these changes Aug 20, 2026

@yuumasato yuumasato left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@abushkin-redhat Looking good. Just one small comment.

Also, please sign the commits so the PR is mergeable.

Comment thread pkg/controller/compliancescan/networkpolicy_test.go Outdated
The operator now creates and reconciles label-scoped NetworkPolicies for its
operand pods (result server, scanners, aggregator, profileparser, rerunner) so
operand traffic is governed at runtime rather than left unrestricted.

- Add the compliance.openshift.io/netpol-managed marker to every operand pod
  template (pod template only for the result-server and profileparser
  Deployments, to avoid mutating their immutable selectors).
- Create three named policies in the operator namespace via get-or-create/update
  (no list/watch, matching the RBAC granted for CMP-4496): default-deny
  (ingress+egress), allow-all-egress, and result-server ingress on 8443 from
  pod-network scanners and host-networked node scanners.
- Reconcile the policies at the start of the scan launching phase, before any
  operand pods are created.
- Read NetworkPolicy directly from the API server (manager client cache
  DisableFor) so a by-name Get does not start an informer list+watch, which the
  operand RBAC does not grant.

Depends on ComplianceAsCode#1313 (CMP-4496) for the NetworkPolicy RBAC.
@openshift-ci

openshift-ci Bot commented Aug 20, 2026

Copy link
Copy Markdown

New changes are detected. LGTM label has been removed.

@openshift-ci

openshift-ci Bot commented Aug 20, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: abushkin-redhat

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

1 similar comment
@openshift-ci

openshift-ci Bot commented Aug 20, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: abushkin-redhat

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@abushkin-redhat

Copy link
Copy Markdown
Collaborator Author

@abushkin-redhat Looking good. Just one small comment.

Also, please sign the commits so the PR is mergeable.

The CMP-4569 commit is signed, just the two commits from PR #1313 are unsigned, but my plan there was to wait for that PR to be merged, and then rebase this one.

@github-actions

Copy link
Copy Markdown

🤖 To deploy this PR, run the following command:

make catalog-deploy CATALOG_IMG=ghcr.io/complianceascode/compliance-operator-catalog:1364-fceeb32ba4330757bb9fbde62a40556a03a96fc8

@openshift-ci

openshift-ci Bot commented Aug 20, 2026

Copy link
Copy Markdown

@abushkin-redhat: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/unit fceeb32 link true /test unit
ci/prow/e2e-aws-parallel fceeb32 link true /test e2e-aws-parallel
ci/prow/e2e-aws-serial fceeb32 link true /test e2e-aws-serial
ci/prow/images fceeb32 link true /test images

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@yuumasato

Copy link
Copy Markdown
Member

@abushkin-redhat Hi, this can be rebased now, #1313 is merged.

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