Building a Secure Multi-Tenant Kubernetes Platform with Namespace Isolation and Network Policies
TL;DR Quick Summary
- Multi-tenancy lets multiple teams share a Kubernetes cluster, but requires deliberate security controls to prevent cross-tenant attacks.
- This guide walks through a namespace-per-tenant model using native Kubernetes features: namespaces for logical isolation, RBAC for fine-grained access, NetworkPolicies for traffic segmentation, and ResourceQuotas for fair resource sharing.
- You’ll get copy-paste-ready YAML examples, a clear implementation sequence, and best practices for hardening your platform against common threats.
Introduction
Kubernetes has become the de facto standard for container orchestration, and with its rise, organizations are increasingly looking to consolidate workloads. Instead of spinning up a separate cluster for every team, product, or customer, multi-tenancy allows you to share a single cluster across multiple “tenants”—reducing infrastructure costs, simplifying operations, and improving resource utilization.
However, sharing a cluster introduces a critical challenge: security. In a multi-tenant environment, one compromised or malicious tenant could potentially access another tenant’s data, exhaust shared resources, or disrupt services. The default Kubernetes configuration is permissive—namespaces provide logical separation, but they do not restrict network traffic or access by default.
This guide provides a practical, production-ready approach to building a secure multi-tenant platform using only native Kubernetes features. We’ll cover:
- Choosing the right tenancy model
- Designing namespace isolation
- Implementing RBAC for access control
- Enforcing NetworkPolicies
- Setting ResourceQuotas and Limits
- Best practices for preventing cross-tenant attacks
By the end, you’ll have a clear blueprint for a secure, multi-tenant Kubernetes platform.
Understanding Multi-Tenancy Models
Before you start writing YAML, you need to decide how you want to isolate your tenants. There are three primary models, each with trade-offs between isolation strength, cost, and operational complexity.
| Model | Isolation Level | Cost | Use Case |
|---|---|---|---|
| Cluster-per-tenant | Strongest (full kernel, network, and API isolation) | Highest (dedicated control plane per tenant) | High-security, regulated environments (e.g., finance, healthcare) |
| Namespace-per-tenant | Logical (API-level isolation, shared kernel) | Low (one cluster shared) | Internal teams, Dev/Test, SaaS with moderate security requirements |
| Partition-per-tenant | Stronger (dedicated node pools, taints/tolerations) | Medium (multiple node groups) | Workloads requiring dedicated compute or compliance (e.g., PCI) |
This guide focuses on the namespace-per-tenant model, as it’s the most common and offers the best balance of security and efficiency for internal teams and many SaaS use cases. The techniques we’ll cover—NetworkPolicies, RBAC, and quotas—are also applicable to the partition model as an additional layer of defense.
Designing Namespace Isolation
The foundation of your multi-tenant platform is the namespace. Each tenant gets its own namespace, which provides a logical boundary for resources like Pods, Services, ConfigMaps, and Secrets.
Naming Conventions and Labels
Adopt a consistent naming convention to make management and automation easier. For example, use the pattern tenant-<name> (e.g., tenant-acme, tenant-globex). Apply a label to each namespace to identify the tenant:
apiVersion: v1
kind: Namespace
metadata:
name: tenant-acme
labels:
tenant: acme
environment: production
Labels are crucial for selecting resources in NetworkPolicies, RBAC bindings, and monitoring.
Initial Setup
Create the namespace and verify it:
kubectl create namespace tenant-acme
kubectl label namespace tenant-acme tenant=acme
Implementing RBAC for Access Control
With namespaces in place, you need to control who can do what within them. Kubernetes RBAC (Role-Based Access Control) is your primary tool. The key principle is least privilege: give users and service accounts only the permissions they absolutely need.
Roles vs. ClusterRoles
- Role: Grants permissions within a specific namespace.
- ClusterRole: Grants permissions cluster-wide (or across all namespaces).
For multi-tenancy, you’ll almost always use Roles and RoleBindings scoped to a tenant’s namespace. This prevents a tenant from seeing or modifying resources in another tenant’s namespace.
Example: Tenant Admin Role
Create a Role that allows a tenant admin to manage most resources within their namespace, but not cluster-level objects:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: tenant-acme
name: tenant-admin
rules:
- apiGroups: ["", "apps", "batch", "networking.k8s.io"]
resources: ["pods", "services", "deployments", "statefulsets", "jobs", "cronjobs", "networkpolicies", "configmaps", "secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["resourcequotas", "limitranges"]
verbs: ["get", "list", "watch"]
Now bind this Role to a user or group (in this case, a group called acme-admins):
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: tenant-acme
name: tenant-admin-binding
subjects:
- kind: Group
name: acme-admins
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: tenant-admin
apiGroup: rbac.authorization.k8s.io
Important: Never grant
cluster-adminto tenant users. If you need to delegate cluster-level tasks (like creating namespaces), do so via a separate, tightly-controlled ClusterRole and a small group of platform administrators.
Enforcing Network Policies
Namespace isolation via RBAC controls API access, but it does not control network traffic. By default, any pod can communicate with any other pod in the cluster, regardless of namespace. This is where NetworkPolicies come in.
NetworkPolicies are firewall rules for your pods. They are implemented by a CNI plugin (e.g., Calico, Cilium, Weave Net). If you’re using a CNI that doesn’t support them (like Flannel in its default mode), you’ll need to switch or add a policy engine.
Step 1: Default Deny-All
The first and most critical policy is to deny all ingress and egress traffic in the tenant namespace. This creates a “zero-trust” baseline.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: tenant-acme
spec:
podSelector: {} # Selects all pods in the namespace
policyTypes:
- Ingress
- Egress
Apply this policy immediately after creating the namespace, before any workloads are deployed.
Step 2: Allow Necessary Traffic
Now you can add policies to allow specific traffic. For example, allow ingress from pods within the same namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-intra-namespace
namespace: tenant-acme
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant: acme
This policy allows traffic only from pods in namespaces labeled tenant: acme (i.e., the tenant’s own namespace).
Step 3: Allow Traffic from Specific Services
If the tenant needs to receive traffic from the ingress controller (e.g., for a web app), allow ingress from the ingress-nginx namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-ingress
namespace: tenant-acme
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- port: 80
protocol: TCP
Visualizing the Policy Architecture
Here’s a high-level view of how NetworkPolicies segment traffic in your multi-tenant cluster:
flowchart TD
subgraph "Cluster"
subgraph "Namespace: tenant-acme"
A[Pod: app-web] -->|Allowed: intra-ns| B[Pod: app-api]
C[External User] -->|HTTPS| D[Ingress Controller]
D -->|Allowed: port 80| A
end
subgraph "Namespace: tenant-globex"
E[Pod: app-backend]
end
subgraph "Namespace: kube-system"
F[CoreDNS]
end
A -.->|DENIED: cross-tenant| E
B -.->|DENIED: external egress| G[Internet]
B -->|Allowed: DNS| F
end
Setting Resource Quotas and Limits
Multi-tenancy requires fair resource sharing. Without quotas, a single tenant could consume all cluster CPU or memory, causing a denial-of-service for other tenants.
ResourceQuota
A ResourceQuota sets aggregate limits for a namespace. Here’s an example:
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: tenant-acme
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
persistentvolumeclaims: "10"
pods: "50"
services: "20"
secrets: "50"
configmaps: "50"
LimitRange
A LimitRange sets default request/limit values for individual pods and containers, ensuring that containers without explicit resource declarations don’t run unbounded.
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-limits
namespace: tenant-acme
spec:
limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "2"
memory: 4Gi
min:
cpu: 50m
memory: 64Mi
type: Container
This configuration ensures that:
- Every container gets a default CPU request of 100m and limit of 500m.
- No single container can exceed 2 CPUs or 4Gi of memory.
- No container can request less than 50m CPU or 64Mi memory (preventing starvation of the scheduler).
Best Practices for Preventing Cross-Tenant Attacks
Namespace isolation, RBAC, NetworkPolicies, and quotas provide a solid foundation. However, a truly secure multi-tenant platform requires additional hardening:
1. Pod Security Admission (PSA)
Use PSA (the successor to PodSecurityPolicies) to enforce security standards on pods. Apply the restricted profile to tenant namespaces:
kubectl label --overwrite ns tenant-acme \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest
This prevents pods from running as root, using privileged containers, or mounting host paths.
2. Disable Privileged Containers and hostNetwork
Ensure your NetworkPolicies and PSA settings block hostNetwork and privileged mode. A pod with hostNetwork can bypass NetworkPolicies entirely.
3. Read-Only Root Filesystems
Encourage (or enforce) readOnlyRootFilesystem: true in your pod security context. This limits the damage an attacker can do if they compromise a container.
4. Regular Auditing with kube-bench
Run kube-bench regularly to check your cluster against the CIS Kubernetes Benchmark. This catches common misconfigurations.
5. Consider a Service Mesh for mTLS
For defense-in-depth, a service mesh like Istio or Linkerd can provide mutual TLS (mTLS) between pods, encrypting traffic and providing identity-based authorization beyond what NetworkPolicies offer.
Implementation Sequence: A Step-by-Step Blueprint
Here’s the recommended order for onboarding a new tenant:
flowchart LR
A[Create Namespace] --> B[Apply Labels]
B --> C[Apply Default-Deny NetworkPolicy]
C --> D[Create ResourceQuota & LimitRange]
D --> E[Create RBAC Role & Binding]
E --> F[Apply Pod Security Admission Labels]
F --> G[Deploy Tenant Workloads]
G --> H[Apply Specific NetworkPolicies]
H --> I[Monitor & Audit]
- Create the namespace with proper labels.
- Apply the default-deny NetworkPolicy immediately.
- Set ResourceQuotas and LimitRanges to define resource boundaries.
- Create Roles and RoleBindings for tenant users.
- Apply PSA labels to enforce security standards.
- Deploy tenant workloads.
- Apply specific NetworkPolicies to allow required traffic.
- Continuously monitor and audit using tools like kube-bench and policy reports.
Conclusion
Building a secure multi-tenant Kubernetes platform is not a single configuration—it’s a layered defense strategy. By combining namespaces for logical isolation, RBAC for access control, NetworkPolicies for traffic segmentation, and ResourceQuotas for fair resource sharing, you can safely share a cluster among multiple tenants.
Key takeaways:
- Start with a default-deny network posture and open up only what’s needed.
- Apply the principle of least privilege to all RBAC configurations.
- Set quotas early to prevent resource exhaustion.
- Use Pod Security Admission to enforce secure pod configurations.
- Audit regularly—security is an ongoing process.
While namespace-per-tenant isolation is powerful, remember its limitations: it does not protect against kernel-level exploits. For high-security workloads, consider node-level isolation or dedicated clusters. For most internal and SaaS scenarios, however, this approach offers an excellent balance of security, efficiency, and operational simplicity.
FAQ
What is the difference between namespace isolation and network policies?
Namespace isolation provides logical separation of resources (like pods, services) and access control via RBAC, but does not restrict network traffic by default. Network policies enforce rules on pod-to-pod communication, allowing you to block traffic between namespaces unless explicitly allowed.
How do I ensure one tenant cannot access another tenant’s resources?
Combine namespace isolation with RBAC: create roles limited to specific namespaces and bind them to tenant users/groups. Additionally, use NetworkPolicies to deny cross-namespace traffic by default, and only allow necessary communication.
Can I use NetworkPolicies to block all traffic from other namespaces?
Yes, you can create a NetworkPolicy that selects all pods in your namespace and only allows ingress from pods with specific labels (e.g., your own namespace). This effectively blocks cross-namespace traffic.
What are the limitations of namespace-based multi-tenancy?
Namespaces do not provide strong isolation against malicious tenants who can exploit kernel vulnerabilities or use hostNetwork. For high-security environments, consider cluster-per-tenant or node-level isolation with dedicated nodes and taints.
How do I manage resource quotas for multiple tenants?
Use ResourceQuota objects per namespace to set limits on total CPU, memory, and number of objects. This prevents one tenant from consuming all cluster resources. Also, use LimitRange to set default requests/limits for pods.