Skip to content

Access & Exposure

Two coupled hardening controls, both Free on every install: an in-product IP access layer (allowlist / denylist / auto-ban) with a sliding idle timeout, and a service-only exposure mode so you can front KubeManta with your own edge/WAF.

Defense in depth, not a WAF

App-level IP filtering has no L7 pattern detection, no DDoS absorption, and no geo-fencing. For internet-facing production, put a real edge/WAF in front — service-only mode (below) is designed to make that easy.


In-product IP access control

IP access control runs before authentication, so a denied IP is rejected with a flat 403 before any login work happens. It resolves the real client IP (honoring security.trustedProxyCidrs) and evaluates it against a policy that is stored encrypted at rest in-cluster.

Policy

Field Meaning
allowlist If non-empty, only these IPs/CIDRs are allowed. Empty = allow all.
denylist Always wins — a denylisted IP is blocked even if allowlisted.
auto_ban Threshold / window / ban-duration for auth_flood, the one automatic IP ban.
session_idle_timeout Sliding idle timeout in seconds (see below).

Live bans

Auto-ban hits and manual bans are recorded durably, each with a reason, a hit count, and an expiry (a ban with no expiry is permanent). Bans survive pod restarts.

Only auth_flood bans an address automatically, and it responds to volumetric abuse rather than to wrong passwords. A policy saved by an older install may still list failed_login or rate_abuse; those are inert and are left in place rather than rejected, so an upgrade is not broken by a setting that no longer does anything. The admin UI renders only the live trigger.

Brute force is handled separately and does not ban an address: repeated failures are counted against the pair of account and IP, so one person failing to sign in cannot lock out everyone else behind the same office egress or VPN.

Fail-safe & exemptions

  • Fail-safe: a config-load or database error degrades to allow-all rather than lock-all, so a mistake in this policy cannot lock you out of your own cluster. Enforcement against volumetric abuse belongs at your edge.
  • Loopback is always exempt, so kubectl exec and port-forward can never be locked out. RFC1918 private ranges are exempt by default and can be switched off with exempt_private_ranges once you are sure your edge sets the real client IP.
  • /healthz is always skipped; /auth/* is not (so a banned IP can't even reach the login form).

Admin API

Endpoint Purpose
GET/PUT /admin/access-control Read / write the policy.
POST /admin/access-control/bans Add a manual ban.
DELETE /admin/access-control/bans/{ip} Lift a ban.

All admin-gated, and surfaced in Admin → Access Control.

Anti-lockout escape hatch

If you lock yourself out, a CLI escape hatch ships day one:

kubectl exec -n kubemanta-system deploy/kubemanta-agent -- python3 -m app.clear_bans
# add --reset-policy to also reset access_control back to allow-all

Sliding idle timeout

Sessions carry a last-activity marker, so the idle timeout is enforced on every request:

  • A session that has been idle longer than session_idle_timeout is rejected (401); an active session is transparently extended (sliding).
  • The absolute 24-hour session lifetime is the hard ceiling — both checks apply, the stricter one wins.

Seed it with security.sessionIdleTimeout (default 1800); it's runtime-editable in Admin → Access Control (the stored value wins after first load).

sessionIdleTimeout: 0 disables the idle check

0 must render as SESSION_IDLE_TIMEOUT="0" (disabled) — the absolute 24-hour TTL still applies. A client-side inactivity timer in the UI is the UX layer; this server-side check is the backstop.


Service-only exposure (ingress.enabled=false)

Set ingress.enabled=false to render only the agent + UI Services — no Ingress, BackendConfig, or ManagedCertificate. Point your own ingress / load balancer / Gateway / WAF at them.

ingress:
  enabled: false
service:
  type: ClusterIP          # or NodePort | LoadBalancer
  annotations:             # e.g. internal-only LB
    networking.gke.io/load-balancer-type: "Internal"
ui:
  service:
    type: ClusterIP
    annotations: {}

Routing when you front it yourself:

  • The terminal WebSocket path /api/agent/terminal/ws must reach the agent Service directly.
  • All other traffic goes to the UI Service.

You MUST set security.trustedProxyCidrs

Service-only mode is only correct if security.trustedProxyCidrs is set to your edge's egress CIDR(s). Otherwise KubeManta attributes every request to the proxy IP, which collapses audit attribution and makes every allowlist and denylist entry mean the edge rather than the person behind it.

security:
  trustedProxyCidrs: "10.0.0.0/8"   # your edge's egress CIDR(s), comma-separated

Set this to your edge and nothing wider — a range that covers your pod network would let any workload in the cluster choose the address recorded against it. Spoofed X-Forwarded-For from outside the trusted ranges is rejected.


Out of scope

BYO ingress-class passthrough, Gateway API HTTPRoute and attach-to-existing-LB are outside this page. For identity-provider login see Enterprise SSO; for multi-factor authentication see Users & Access.