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 withsystemctl unmask rpcbind.socket.
What the firewall opens
UFW is installed with a default-deny incoming policy. The following ports are allowed:
| Port | Reason |
|---|---|
| Detected SSH port (22 by default) | Reading sshd -T so non-default ports are honoured |
| 80, 443 | Traefik ingress |
| 6443 | k3s 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
kip install --host <ip> --no-harden # leave surplus services running
kip install --host <ip> --no-firewall # do not install UFWWhen 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:
kip cluster harden # uses the current cluster
kip cluster harden --cluster apprunr # specific cluster
kip cluster harden --no-firewall # only disable surplus servicesThe 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:
| Header | Value | Protection |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | Forces HTTPS for 1 year |
X-Frame-Options | SAMEORIGIN | Prevents clickjacking |
X-Content-Type-Options | nosniff | Prevents MIME type sniffing |
X-XSS-Protection | 1; mode=block | Blocks reflected XSS attacks |
Content-Security-Policy | Restricts script, style, image, and connection sources to self | Prevents XSS and data injection |
Referrer-Policy | strict-origin-when-cross-origin | Controls referrer information leakage |
Server / X-Powered-By | Removed | Hides 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:
# 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 500Apps 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 tokipper-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
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:
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:
kip app secret set my-app DATABASE_URL
kip app secret set my-app API_KEYThese are injected as environment variables at runtime, not stored in the image.
Pin image versions
Use specific version tags instead of :latest in production:
# Development
kip app deploy --name api --image registry.example.com/api:latest
# Production
kip app update api --image registry.example.com/api:v1.2.3Scan images for vulnerabilities
Before deploying to production, scan your images for known CVEs:
# Using Trivy (free, open source)
trivy image registry.example.com/api:v1.2.3Kipper 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> KEYwithout=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:
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