Skip to content

Tenant Isolation Checklist

Multi-tenant deployment security guidance for the Agent Governance Toolkit.

Pre-Deployment Checklist

Kubernetes Namespace Isolation

  • [ ] Create dedicated namespace per tenant (kubectl create ns tenant-<id>)
  • [ ] Apply NetworkPolicy restricting cross-namespace traffic
  • [ ] RBAC roles scoped to tenant namespace (no cluster-wide bindings)
  • [ ] Pod Security Standards set to restricted profile
  • [ ] Resource quotas per tenant namespace (CPU, memory, pod count)
  • [ ] Secret encryption at rest enabled (etcd encryption provider)
# NetworkPolicy: deny all cross-namespace ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-cross-namespace
  namespace: tenant-acme
spec:
  podSelector: {}
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector: {}  # same namespace only

Trust Store Separation

  • [ ] Separate trust store per tenant (no shared state)
  • [ ] Trust scores scoped by tenant namespace
  • [ ] No cross-tenant trust propagation without explicit federation
# Per-tenant trust store config
apiVersion: v1
kind: ConfigMap
metadata:
  name: agt-trust-config
  namespace: tenant-acme
data:
  AGT_TRUST_PERSIST_PATH: "/data/trust/tenant-acme.json"
  AGT_TRUST_THRESHOLD: "500"

Audit Log Separation

  • [ ] Per-tenant audit log streams
  • [ ] Audit sink routing by namespace label
  • [ ] Cross-tenant audit queries blocked by RBAC
# Fluent Bit filter for tenant-scoped log routing
[FILTER]
    Name    grep
    Match   kube.*
    Regex   kubernetes.namespace_name ^tenant-acme$

[OUTPUT]
    Name    azure_blob
    Match   kube.*
    Account_name    ${STORAGE_ACCOUNT}
    Container_name  audit-tenant-acme
    Shared_key      ${STORAGE_KEY}
    Path            audit/

Data Residency

Node Affinity for Region Pinning

# Pin tenant workloads to specific region
apiVersion: v1
kind: Pod
metadata:
  name: agt-sidecar
  namespace: tenant-acme
spec:
  nodeSelector:
    topology.kubernetes.io/region: eastus
    data-residency: us-east
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values: [eastus-1, eastus-2]

PersistentVolume Topology Constraints

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: agt-audit-pvc
  namespace: tenant-acme
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: managed-premium-zrs
  resources:
    requests:
      storage: 10Gi
  # Ensure storage stays in tenant's region
  volumeMode: Filesystem

Multi-Tenant Policy Engine

Tenant-Scoped Policies

# Policy scoped to tenant namespace
apiVersion: "1.0"
version: "1.0"
name: tenant-acme-policy
scope: tenant
agent: "*"
rules:
  - name: restrict-data-access
    condition: "tenant_id == 'acme'"
    ruleAction: deny
    description: "Block cross-tenant data access"
    priority: 100

  - name: rate-limit-per-tenant
    condition: "tenant_id == 'acme'"
    ruleAction: rate_limit
    limit: "100/minute"

Cross-Tenant Communication Rules

from agentmesh import PolicyEngine

engine = PolicyEngine()

# Load tenant-specific policy
engine.load_from_yaml(f"policies/tenant-{tenant_id}.yaml")

# Evaluate with tenant context
decision = engine.evaluate(
    action="data.read",
    context={"tenant_id": "acme", "source_tenant": "acme", "target_tenant": "acme"}
)

# Cross-tenant requests denied by default
cross_tenant = engine.evaluate(
    action="data.read",
    context={"tenant_id": "acme", "source_tenant": "acme", "target_tenant": "contoso"}
)
assert cross_tenant.label() == "deny"

Egress Controls

  • [ ] Egress NetworkPolicy restricting outbound traffic
  • [ ] DNS policy limiting resolution to approved domains
  • [ ] Data exfiltration prevention via egress proxy
# Egress NetworkPolicy: allow only governance API + DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-egress
  namespace: tenant-acme
spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              name: agt-system
      ports:
        - port: 443
    - to:  # DNS
        - namespaceSelector: {}
      ports:
        - port: 53
          protocol: UDP