Enterprise Multi-Tenancy

Deploy Snowplane in multi-tenant enterprise environments with namespace isolation, cross-namespace references, and Snowflake resource scoping.


Overview

Snowplane provides a layered isolation model for enterprise multi-tenancy:

Layer Mechanism What It Controls
Kubernetes namespace watchNamespaces, K8s RBAC Which namespaces the operator manages
ProviderConfig binding allowedNamespaces, allowedNamespaceSelector Which namespaces can use a given set of Snowflake credentials
Snowflake scope allowedDatabases, allowedSchemas Which Snowflake databases/schemas a team can target
Snowflake role spec.role, --allowed-roles Which Snowflake permissions are available
Cross-namespace refs databaseRef.namespace, schemaRef.namespace, allowedRefNamespaces Shared infrastructure references across namespaces (restricted by allowedRefNamespaces)

Deployment Models

Model 1: Single Namespace (Simple)

All resources in one namespace. Suitable for small teams or single-project deployments.

snowflake/
├── providerconfig.yaml
├── database.yaml
├── schema.yaml
├── table.yaml
└── view.yaml

No special configuration needed — all references are namespace-local by default.

A platform team manages shared infrastructure in a central namespace. Project teams manage their own resources in separate namespaces, referencing shared infrastructure via cross-namespace refs.

infra/                              team-analytics/
├── providerconfig.yaml             ├── schema.yaml
├── database.yaml (ANALYTICS_DB)    ├── table.yaml
└── warehouse.yaml (COMPUTE_WH)    └── view.yaml

                                    team-ml/
                                    ├── schema.yaml
                                    ├── table.yaml
                                    └── dynamic-table.yaml
# infra/providerconfig.yaml
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: ProviderConfig
metadata:
  name: snowflake-prod
  namespace: infra
spec:
  account: myorg-myaccount
  user: SNOWPLANE_SVC
  role: SNOWPLANE_ADMIN
  authenticator:
    type: KeyPair
    privateKeySecretRef:
      name: snowflake-credentials
      key: private-key
  # Namespace isolation
  allowedNamespaces:
    - infra
    - team-analytics
    - team-ml
  # Or use label selectors for dynamic environments
  allowedNamespaceSelector:
    matchLabels:
      snowplane.io/tenant: "true"
  # Snowflake scope restrictions
  allowedDatabases:
    - ANALYTICS_DB
    - ML_DB
# team-analytics/schema.yaml
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Schema
metadata:
  name: raw-data
  namespace: team-analytics
spec:
  name: RAW_DATA
  providerRef:
    name: snowflake-prod
    namespace: infra              # Cross-namespace ProviderConfig
  databaseRef:
    name: analytics-db
    namespace: infra              # Cross-namespace Database reference

Cross-namespace references require the Snowplane controller to have RBAC access to resources in the target namespace. The default Helm chart grants cluster-wide access. For namespace-scoped RBAC, see Scaling with Namespace Sharding.

Model 3: ProviderConfig per Team

Each team gets its own ProviderConfig with a dedicated Snowflake role and scoping. Provides the strongest isolation.

team-analytics/                     team-ml/
├── providerconfig.yaml             ├── providerconfig.yaml
├── database.yaml                   ├── database.yaml
├── schema.yaml                     ├── schema.yaml
└── table.yaml                      └── dynamic-table.yaml
# team-analytics/providerconfig.yaml
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: ProviderConfig
metadata:
  name: analytics-pc
  namespace: team-analytics
spec:
  account: myorg-myaccount
  user: ANALYTICS_SVC
  role: ANALYTICS_ROLE             # Least-privilege Snowflake role
  authenticator:
    type: KeyPair
    privateKeySecretRef:
      name: analytics-credentials
      key: private-key
  allowedNamespaces:
    - team-analytics               # Only this namespace
  allowedDatabases:
    - ANALYTICS_DB                 # Only this database
  allowedSchemas:
    - ANALYTICS_DB.RAW
    - ANALYTICS_DB.CURATED

Isolation Mechanisms

Namespace Binding

Control which Kubernetes namespaces can use a ProviderConfig:

spec:
  # Static list
  allowedNamespaces:
    - team-a
    - team-b

  # Label selector (OR with static list)
  allowedNamespaceSelector:
    matchLabels:
      env: production
    matchExpressions:
      - key: team
        operator: In
        values: [analytics, ml, data-eng]

When both allowedNamespaces and allowedNamespaceSelector are set, a namespace is permitted if it matches either mechanism (OR semantics).

When allowedNamespaceSelector is set without allowedNamespaces, only namespaces matching the selector are permitted. This is a deny-by-default model.

Database & Schema Scoping

Restrict which Snowflake databases and schemas resources may target:

spec:
  allowedDatabases:
    - ANALYTICS_DB
    - SHARED_DB
  allowedSchemas:
    - ANALYTICS_DB.PUBLIC
    - ANALYTICS_DB.RAW
    - SHARED_DB.ANALYTICS
  • allowedDatabases: Case-insensitive. Empty = all databases allowed.
  • allowedSchemas: Supports "SCHEMA" (any database) or "DATABASE.SCHEMA" format. Empty = all schemas allowed.
  • Resources violating these constraints are rejected with DatabaseNotAllowed or SchemaNotAllowed condition reasons and do not issue any Snowflake SQL.

Cross-Namespace References

All databaseRef, schemaRef, and providerRef fields support an optional namespace field:

spec:
  providerRef:
    name: snowflake-prod
    namespace: infra          # Omit for same-namespace
  databaseRef:
    name: shared-db
    namespace: infra          # Omit for same-namespace
  schemaRef:
    name: public-schema
    namespace: infra          # Omit for same-namespace

When namespace is omitted, the reference resolves within the same namespace as the referencing resource (backward-compatible).

Cross-namespace references preserve readiness gating — the controller checks that the referenced resource exists and has Ready=True before proceeding. This prevents opaque SQL errors when dependencies aren’t ready yet.

Restricting Cross-Namespace References

Use allowedRefNamespaces on ProviderConfig to restrict which namespaces cross-namespace refs may target:

apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: ProviderConfig
metadata:
  name: default
spec:
  allowedRefNamespaces:
    - SAME          # Allow refs within the same namespace
    - infra         # Allow refs to the shared infrastructure namespace
  # ...
Value Semantics
(empty list) No restriction — all namespaces allowed (default)
"*" All namespaces explicitly allowed
"SAME" Only same-namespace references allowed
"ns-a", "ns-b" Only the listed namespaces allowed

Violations are rejected with a RefNamespaceNotAllowed condition reason. No Snowflake SQL is issued.

Snowflake Role Restriction

Restrict which Snowflake roles can be used in ProviderConfig resources:

# Helm values
controller:
  allowedRoles: "SYSADMIN,SNOWPLANE_ADMIN,ANALYTICS_ROLE"

Any ProviderConfig using a role not in this list is rejected. This prevents teams from escalating to ACCOUNTADMIN or SECURITYADMIN.

Per-resource role overrides (spec.useRole) are validated against the same allowlist.


Pre-flight Validation

The controller automatically validates that referenced Snowflake databases and schemas exist before issuing CREATE commands:

  • CR references (databaseRef/schemaRef): Existence validated during reference resolution — the referenced CR must be Ready=True.
  • Raw strings (databaseName/schemaName): The controller issues SHOW DATABASES LIKE/SHOW SCHEMAS LIKE queries to verify existence before CREATE.

This provides clear DependencyNotReady conditions instead of opaque SQL errors, regardless of whether teams use cross-namespace refs or raw Snowflake identifiers.


GitOps Integration

Argo CD — Namespace per Team

gitops-repo/
├── infra/                    # Platform team — synced by infra AppSet
│   ├── providerconfig.yaml
│   ├── database.yaml
│   └── warehouse.yaml
├── team-analytics/           # Analytics team — synced by team AppSet
│   ├── schema.yaml
│   ├── table.yaml
│   └── view.yaml
└── team-ml/                  # ML team — synced by team AppSet
    ├── schema.yaml
    └── dynamic-table.yaml
# Argo CD ApplicationSet — one Application per team
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: snowplane-teams
spec:
  generators:
    - git:
        repoURL: https://github.com/myorg/gitops-repo
        directories:
          - path: 'team-*'
  template:
    metadata:
      name: 'snowplane-'
    spec:
      destination:
        namespace: ''
      source:
        repoURL: https://github.com/myorg/gitops-repo
        path: ''
      syncPolicy:
        automated:
          prune: true

See the Argo CD guide for sync waves and health checks.

Flux — Namespace per Team

# Flux Kustomization per team
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: team-analytics
  namespace: flux-system
spec:
  sourceRef:
    kind: GitRepository
    name: gitops-repo
  path: ./team-analytics
  targetNamespace: team-analytics
  dependsOn:
    - name: infra      # Ensure infra resources exist first

See the Flux guide for detailed setup.


Policy Enforcement

Kyverno — Require Namespace Scoping

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-providerconfig-scoping
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-namespace-restriction
      match:
        resources:
          kinds:
            - ProviderConfig
      validate:
        message: >-
          ProviderConfig must specify allowedNamespaces or
          allowedNamespaceSelector for multi-tenant isolation.
        anyPattern:
          - spec:
              allowedNamespaces: "?*"
          - spec:
              allowedNamespaceSelector: "?*"

Kyverno — Enforce Database Scoping

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-database-scoping
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-allowed-databases
      match:
        resources:
          kinds:
            - ProviderConfig
      validate:
        message: "ProviderConfig must restrict allowedDatabases."
        pattern:
          spec:
            allowedDatabases: "?*"

OPA/Gatekeeper — Role Restriction

See the Production Guide for OPA/Gatekeeper examples.


Namespace Sharding

For very large deployments (10K+ resources), run multiple Snowplane instances with non-overlapping namespace sets:

# Instance 1: Analytics teams
helm install snowplane-analytics charts/snowplane/ \
  --set watchNamespaces="team-analytics,team-bi,team-reporting" \
  --set leaderElectionID=snowplane-analytics

# Instance 2: ML teams
helm install snowplane-ml charts/snowplane/ \
  --set watchNamespaces="team-ml,team-data-science" \
  --set leaderElectionID=snowplane-ml

Each instance only watches its assigned namespaces, providing horizontal scaling and blast radius isolation.

Namespace sets must not overlap — two instances watching the same namespace will conflict on leader election and reconciliation.


Further Reading