Skip to content

Migrate scan-scheduling CronJobs off unauthenticated /v1/triggerAction to OperatorCommand CRDs #412

Description

@matthyx

Background

/v1/triggerAction (restapihandler/triggeraction.go, port 4002) is an unauthenticated HTTP endpoint — no caller identity check, and its Service (operator:4002, named trigger-port) carries no NetworkPolicy. Verified live on armo-dev-stage: an anonymous, tokenless POST from a throwaway in-cluster pod returns 200/ok with no auth challenge at any layer.

Its only legitimate callers, confirmed live:

CronJob commandName sent
kubescape-scheduler kubescapeScan
kubevuln-scheduler scan
registry-scan CronJob (created dynamically by watcher/registryhandler.go when registry scanning is configured) scanRegistryV2

All three run quay.io/kubescape/http-request, POSTing a fixed request body to operator:4002/v1/triggerAction with automountServiceAccountToken: false — i.e. no credentials at all.

#411 adds a CommandName allowlist as a stopgap, closing off the immediate risk (an unauthenticated caller directing operatorAction/remediation commands, including patch from #410, at the operator's cluster-wide remediation RBAC). But the endpoint itself remains unauthenticated for the scan commands it does allow.

The real fix

The operator already has an authenticated, RBAC-gated channel for exactly this kind of command delivery: the synchronizer component holds its own K8s ServiceAccount with a ClusterRole explicitly granting create/update/patch/delete on operatorcommands.kubescape.io, reached only through its own authenticated outbound connection to the backend. It creates OperatorCommand custom resources directly, which the operator's existing CRD watcher (watcher/commandswatcher.go) already picks up — no HTTP listener involved.

The scan-scheduling CronJobs should do the same instead of an unauthenticated HTTP callback:

  1. Grant kubescape-scheduler's and kubevuln-scheduler's ServiceAccounts (kubescape and kubevuln — they already exist) a narrowly-scoped Role: create on operatorcommands.kubescape.io, namespaced to kubescape only. (The registry-scan CronJob, created dynamically, would need the same.)
  2. Replace the http-request container with something that creates the OperatorCommand CR directly (e.g. a small kubectl apply-based image + script, the same pattern the chart's own certgen-create/certgen-patch init containers already use for a different resource).
  3. Once migrated, delete /v1/triggerAction entirely — the route in restapihandler/restapi.go, and ActionRequest/HandleActionRequest in restapihandler/triggeraction.go — removing the unauthenticated HTTP surface instead of just narrowing it.

This is strictly better than adding application-level auth (TokenReview/SubjectAccessReview) to the endpoint: it reuses Kubernetes' own authentication and authorization, which is already the trust boundary for the equivalent synchronizer-driven path, and it removes the attack surface rather than policing it.

Scope note

Steps 1–2 require changes to the kubescape-operator Helm chart (a separate repo from this one) — the CronJob templates, their ServiceAccount RBAC, and the request-body ConfigMaps all live there. Step 3 (deleting the endpoint) is this repo's change, and should land only once the chart-side migration has shipped and rolled out, so no live CronJob is left calling a route that no longer exists.

Related

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions