Skip to content

Security

Kipper takes security seriously. Every cluster is hardened by default with production-grade security controls, no configuration required.

Shared responsibility

Kipper provides a strong baseline of infrastructure-level security: encrypted traffic, security headers, rate limiting, isolated namespaces, and automatic backups. However, security is ultimately the responsibility of the development and operations teams building on top of Kipper.

No platform can protect against insecure application code, weak passwords, leaked credentials, or misconfigured services. Kipper gives you the foundation, and you are responsible for building securely on top of it. This includes writing secure application code, managing secrets carefully, keeping dependencies updated, and following the principle of least privilege.

We encourage you to treat Kipper's security features as a baseline, not a guarantee. Review your own application security regularly and follow the best practices documented below.

What Kipper provides out of the box

TLS everywhere

All traffic is encrypted with TLS certificates issued automatically by Let's Encrypt via cert-manager. HTTP requests are redirected to HTTPS.

Host hardening

Before installing Kubernetes, kip install audits the server for surplus services exposed on public interfaces and disables them. It then installs a host firewall with rules that work correctly with k3s pod networking.

This matters because Kipper installs on whatever Linux server you point it at, and many distro defaults leave services exposed on the public network that you wouldn't want there. A default Ubuntu install ships nfs-common (pulled in by Longhorn), which pulls in rpcbind listening on 0.0.0.0:111. Open portmappers are abuse vectors for DDoS reflection and routinely trigger upstream-provider abuse reports. Kipper closes this by default so you don't have to know it exists.

What gets hardened

  • rpcbind. Disabled and the socket is masked so it cannot be re-enabled by a package upgrade. Reversible with systemctl unmask rpcbind.socket.

What the firewall opens

UFW is installed with a default-deny incoming policy. The following ports are allowed:

PortReason
Detected SSH port (22 by default)Reading sshd -T so non-default ports are honoured
80, 443Traefik ingress
6443k3s API

Pod and service CIDRs (10.42.0.0/16, 10.43.0.0/16) are also allowed so kube-proxy and Flannel networking work. UFW's DEFAULT_FORWARD_POLICY is set to ACCEPT so pod-to-pod and pod-to-service traffic is not blocked.

Existing firewall detected

If kip install finds UFW or firewalld already active on the host, it skips its own firewall step rather than layer rules on top of someone else's policy. The installer prints a notice that your firewall configuration is now your responsibility, and the rules need to allow 22, 80, 443, and 6443.

Cloud-side firewalls (Hetzner Cloud Firewall, AWS Security Groups, etc.) are external to the host and are not detected by Kipper. If you use one, make sure those four ports are reachable on the server.

Opting out

bash
kip install --host <ip> --no-harden     # leave surplus services running
kip install --host <ip> --no-firewall   # do not install UFW

When opted out, kip install still prints any findings so you know what is being left in place. Use these flags only when you manage host security yourself.

Retro-fitting an existing cluster

For clusters installed before host hardening was the default, apply the same defaults in place:

bash
kip cluster harden                    # uses the current cluster
kip cluster harden --cluster apprunr  # specific cluster
kip cluster harden --no-firewall      # only disable surplus services

The command is idempotent. It runs the same audit as kip install, then disables rpcbind and configures UFW (skipping the firewall step if another firewall is already active).

Security headers

Every response from your applications includes security headers enforced at the ingress level:

HeaderValueProtection
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadForces HTTPS for 1 year
X-Frame-OptionsSAMEORIGINPrevents clickjacking
X-Content-Type-OptionsnosniffPrevents MIME type sniffing
X-XSS-Protection1; mode=blockBlocks reflected XSS attacks
Content-Security-PolicyRestricts script, style, image, and connection sources to selfPrevents XSS and data injection
Referrer-Policystrict-origin-when-cross-originControls referrer information leakage
Server / X-Powered-ByRemovedHides server technology from attackers

Each app gets its own Traefik middleware with these headers. You do not need to configure them in your application.

Customising security settings per app

The defaults work for most applications. Settings can be changed per app from the web console (app detail → Settings tab) or the CLI:

bash
# Disable security headers for a specific app
kip app deploy --name api --image myimg --port 3000 --no-security-headers

# Set a custom rate limit (requests per second)
kip app deploy --name public-api --image myimg --port 8080 --rate-limit 500

Apps without these flags use the cluster defaults (security headers enabled, 100 req/s rate limit).

CSP allowlist

The default Content Security Policy blocks external resources. If your app loads fonts, stylesheets, scripts, or connects to APIs on other domains, add them to the CSP allowlist.

From the web console: Open the app's Settings tab → enter comma-separated domains in the CSP allowlist field → Save.

Example: To load Google Fonts, add fonts.googleapis.com to the allowlist. The domains are added to style-src, font-src, script-src, and connect-src directives.

Functions have the same CSP settings. Open the function detail panel, then the Settings tab.

TIP

Only add domains you trust. Each allowlisted domain can serve scripts, styles, and fonts to your users.

Rate limiting

All endpoints are rate-limited to 100 requests per second per IP address with a burst allowance of 200. This protects against:

  • Brute force attacks on login endpoints
  • Basic DDoS attacks
  • Credential stuffing
  • API abuse

The rate limit applies at the ingress level before traffic reaches your application.

Pod Security Standards

Kipper enforces the Kubernetes Pod Security Standards baseline profile on all user namespaces. This warns when deployments attempt to:

  • Run containers as root
  • Use privileged mode
  • Access the host network or host PID namespace
  • Use host path volumes
  • Escalate privileges

System namespaces (kube-system, monitoring, etc.) are excluded from enforcement to allow infrastructure components to function.

Authentication

The web console is protected by Dex (OAuth2/OIDC identity provider) with bcrypt-hashed passwords. API access requires a valid JWT token.

Secrets management

Application secrets are stored as Kubernetes Secrets, encrypted at rest by k3s. They are:

  • Never returned in API list responses (only revealed on explicit request)
  • Scoped to the project namespace (one project cannot access another's secrets)
  • Support previous version tracking and rollback
  • Injected into containers via envFrom (not visible in deployment YAML)

Revealing stored credentials

Git credentials and container-registry credentials stored under Settings are masked in both the console UI and the list API. If you lose your copy of a token, you can recover it two ways:

  • In the console: click the eye icon next to a credential in Settings. You'll be asked to re-enter your password before the value is shown. It stays visible for 30 seconds, then re-masks.
  • From the CLI: run kip credentials get <name>. This reads the secret directly from the cluster, so it requires kubeconfig access to kipper-system (effectively cluster-admin).

Only users with the admin role can use the console reveal flow. Both paths log an audit line on success and on failed attempts.

Namespace isolation

Each project environment runs in its own Kubernetes namespace. This provides logical isolation: each namespace has its own Deployments, Services, Secrets, and ConfigMaps. A project cannot access another project's secrets or environment variables.

Network isolation is not enforced by default

While resources are logically separated, pods in one namespace can make network requests to services in another namespace if they know the DNS name (e.g. service.other-namespace.svc.cluster.local). This is standard Kubernetes behaviour.

For full network isolation (blocking cross-namespace traffic), NetworkPolicies are needed. Kipper does not install them by default. If you need strict environment separation today (e.g. preventing test from reaching prod databases), the safest option is to run those environments on separate clusters.

Automatic backups

Daily backups at 3:00 AM include all Kubernetes resources and persistent volume data (databases). Backups are retained for 7 days with one-click restore.

Docker image best practices

Kipper works with any Docker image, but for production deployments we recommend following these security practices:

Run as a non-root user

dockerfile
FROM node:20-alpine

# Create a non-root user
RUN addgroup -S app && adduser -S app -G app

WORKDIR /app
COPY --chown=app:app . .
RUN npm ci --production

# Switch to non-root user
USER app

EXPOSE 3000
CMD ["node", "server.js"]

For Java applications:

dockerfile
FROM eclipse-temurin:21-jre-alpine

RUN addgroup -S app && adduser -S app -G app

WORKDIR /app
COPY --chown=app:app build/libs/*.jar app.jar

USER app

EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Use minimal base images

Prefer Alpine-based images (node:20-alpine, eclipse-temurin:21-jre-alpine) over full Debian images. Smaller images have fewer packages and a smaller attack surface.

Don't store secrets in images

Never bake secrets, API keys, or passwords into Docker images. Use Kipper's secret management instead:

bash
kip app secret set my-app DATABASE_URL
kip app secret set my-app API_KEY

These are injected as environment variables at runtime, not stored in the image.

Pin image versions

Use specific version tags instead of :latest in production:

bash
# Development
kip app deploy --name api --image registry.example.com/api:latest

# Production
kip app update api --image registry.example.com/api:v1.2.3

Scan images for vulnerabilities

Before deploying to production, scan your images for known CVEs:

bash
# Using Trivy (free, open source)
trivy image registry.example.com/api:v1.2.3

Kipper does not run image scanning automatically. Build scanning into your CI pipeline so vulnerable images never reach the cluster.

Basic authentication

You can password-protect any app with HTTP basic auth. This is useful for staging environments, internal tools, or documentation sites that aren't ready for public access yet.

Basic auth is not a substitute for proper authentication (Dex, SSO). It's a simple access gate. Use it when you need a quick way to keep casual visitors out, not for production security.

Enabling via the console

Open the app's Settings tab and scroll to Basic authentication. Enter a username and password, then click Add user. The app is immediately protected. Visitors see a browser login prompt.

You can add multiple users. Each one gets their own credentials.

To remove basic auth entirely, click Remove all. The password prompt disappears and the app is publicly accessible again.

How it works

Kipper creates a Traefik basicAuth middleware that checks credentials before requests reach your app. Usernames and bcrypt-hashed passwords are stored in a Kubernetes Secret named {app}-basic-auth in the app's namespace.

Your app never sees the authentication. Traefik handles it at the ingress level.

Limitations

  • No per-user access control. All users with valid credentials see the same content.
  • No session management. The browser sends credentials on every request.
  • Credentials are sent base64-encoded (not encrypted) in the Authorization header. Always use HTTPS.
  • For proper user authentication with sessions, tokens, and roles, use Dex and the built-in auth system.

Your responsibilities

Regardless of what infrastructure-level protections Kipper provides, your team is responsible for:

  • Secure application code: parameterised queries, input validation, output encoding, CSRF protection
  • Dependency management: keeping libraries and base images up to date, monitoring for CVEs
  • Secret hygiene: never committing credentials to git, rotating secrets regularly, using the hidden prompt (kip app secret set <app> KEY without =VALUE)
  • Access control: limiting who can deploy to production, using strong passwords, enabling 2FA where available
  • Monitoring: reviewing Grafana dashboards and Loki logs for suspicious activity

Verifying your security headers

You can verify that security headers are active on your deployment:

bash
curl -sI https://your-app.kipper.run | grep -i 'strict-transport\|x-frame\|x-content\|x-xss\|content-security\|referrer'

Expected output:

strict-transport-security: max-age=31536000; includeSubDomains; preload
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
content-security-policy: default-src 'self'; ...
referrer-policy: strict-origin-when-cross-origin

Released under the Apache 2.0 License.