cozystack/packages/apps/tenant
myasnikovdaniil cbc79600dd
[linstor-gui] Add Keycloak-protected Ingress for the UI (#2390)
## What this PR does

Stacked on top of #2382. Publishes the linstor-gui UI on
`https://linstor-gui.<root-host>` behind the cluster Keycloak realm
using the same oauth2-proxy gatekeeper pattern that the dashboard uses,
so the LINSTOR REST API is never exposed unauthenticated through
Ingress.

The Ingress is rendered only when **both** gates are satisfied:
- `_cluster.oidc-enabled == "true"`
- `linstor-gui` is listed in `_cluster.expose-services` (i.e. in
`publishing.exposedServices` in the core cozystack values)

If OIDC is disabled cluster-wide we intentionally do **not** ship an
unauthenticated ``token-proxy`` fallback — unlike dashboard, there is no
reason to front a raw storage-management API with k8s bearer tokens.
Operators can still reach the UI via \`kubectl port-forward\` to the
ClusterIP service.

## What's new

- \`templates/gatekeeper.yaml\` — oauth2-proxy Deployment (OIDC mode),
upstream \`linstor-gui.<ns>.svc:80\`
- \`templates/gatekeeper-svc.yaml\` — ClusterIP :8000 in front of it
- \`templates/gatekeeper-sa.yaml\` — dedicated SA, no auto-token
- \`templates/keycloakclient.yaml\` — persistent \`linstor-gui-client\`
+ \`linstor-gui-auth-config\` Secrets, \`KeycloakClient\` CRD that
auto-provisions the OIDC client with \`redirectUris:
[/oauth2/callback/*]\`
- \`templates/ingress.yaml\` — Ingress to the gatekeeper Service with
cert-manager ClusterIssuer, gated on expose-services + oidc-enabled
- \`tests/ingress_auth_test.yaml\` — unit tests for each conditional
branch, KeycloakClient rendering, oauth2-proxy args

README updated with Option 1 (Keycloak-protected Ingress) and Option 2
(port-forward).

### Release note

\`\`\`release-note
Added an opt-in Keycloak-protected Ingress for the linstor-gui package.
Add \`linstor-gui\` to \`publishing.exposedServices\` to publish
\`https://linstor-gui.<root-host>\` behind the cluster OIDC realm.
\`\`\`

## Test plan

- [ ] Deploy to dev10 with \`linstor-gui\` added to
\`publishing.exposedServices\`
- [ ] Verify cert-manager issues the TLS certificate
- [ ] Open \`https://linstor-gui.<root-host>\` in a browser, confirm
Keycloak login challenge
- [ ] After login, confirm LINSTOR node list loads via the nginx → mTLS
proxy
- [ ] Verify helm-unittest cases pass in CI
- [ ] Sanity-check that when \`oidc-enabled=false\` or the service is
not in \`expose-services\`, the Ingress + gatekeeper resources are
skipped
2026-04-15 17:52:29 +05:00
..
charts Add helper function to generate subjects 2025-06-16 19:19:57 +03:00
logos Update dashboard icons (#274) 2024-08-12 14:47:11 +02:00
templates [tenant] Widen linstor-gui egress to whole cozy-linstor namespace 2026-04-14 12:34:15 +05:00
.helmignore Ship all logos with Cozystack 2024-07-16 17:31:52 +02:00
Chart.yaml Remove versions_map logic 2025-09-24 12:32:37 +02:00
Makefile [docs] Added go types codegeneration for managed apps 2026-03-25 15:57:13 +05:00
README.md docs(tenant): align chart README with tenant naming rules 2026-04-12 14:16:41 +03:00
values.schema.json [docs] Updated app go types 2026-03-25 15:57:25 +05:00
values.yaml feat(tenant): add schedulingClass parameter for tenant workloads 2026-03-15 22:00:25 +05:00

Tenant

A tenant is the main unit of security on the platform. The closest analogy would be Linux kernel namespaces.

Tenants can be created recursively and are subject to the following rules:

Tenant naming

Tenant names must be alphanumeric:

  • Lowercase letters (a-z) and digits (0-9) only
  • Must start with a lowercase letter
  • Dashes (-) are not allowed, unlike with other services
  • Maximum length depends on the cluster configuration (Helm release prefix and root domain)

This restriction exists to keep consistent naming in tenants, nested tenants, and services deployed in them. A tenant cannot be named foo-bar because parsing internal resource names like tenant-foo-bar would be ambiguous.

For example:

  • The root tenant is named root, but internally it's referenced as tenant-root.
  • A nested tenant could be named foo, which would result in tenant-foo in service names and URLs.

Unique domains

Each tenant has its own domain. By default, (unless otherwise specified), it inherits the domain of its parent with a prefix of its name. For example, if the parent had the domain example.org, then tenant-foo would get the domain foo.example.org by default.

Kubernetes clusters created in this tenant namespace would get domains like: kubernetes-cluster.foo.example.org

Example:

tenant-root (example.org)
└── tenant-foo (foo.example.org)
    └── kubernetes-cluster1 (kubernetes-cluster1.foo.example.org)

Nesting tenants and reusing parent services

Tenants can be nested. A tenant administrator can create nested tenants using the "Tenant" application from the catalogue.

Higher-level tenants can view and manage the applications of all their children tenants. If a tenant does not run their own cluster services, it can access ones of its parent.

For example, you create:

  • Tenant tenant-u1 with a set of services like etcd, ingress, monitoring.
  • Tenant tenant-u2 nested in tenant-u1.

Let's see what will happen when you run Kubernetes and Postgres under tenant-u2 namespace.

Since tenant-u2 does not have its own cluster services like etcd, ingress, and monitoring, the applications running in tenant-u2 will use the cluster services of the parent tenant.

This in turn means:

  • The Kubernetes cluster data will be stored in etcd for tenant-u1.
  • Access to the cluster will be through the common ingress of tenant-u1.
  • Essentially, all metrics will be collected in the monitoring from tenant-u1, and only that tenant will have access to them.

Example:

tenant-u1
├── etcd
├── ingress
├── monitoring
└── tenant-u2
    ├── kubernetes-cluster1
    └── postgres-db1

Parameters

Common parameters

Name Description Type Value
host The hostname used to access tenant services (defaults to using the tenant name as a subdomain for its parent tenant host). string ""
etcd Deploy own Etcd cluster. bool false
monitoring Deploy own Monitoring Stack. bool false
ingress Deploy own Ingress Controller. bool false
seaweedfs Deploy own SeaweedFS. bool false
schedulingClass The name of a SchedulingClass CR to apply scheduling constraints for this tenant's workloads. string ""
resourceQuotas Define resource quotas for the tenant. map[string]quantity {}

Configuration

Resource Quotas

The resourceQuotas parameter allows you to limit resources available to the tenant. Supported keys include:

Compute resources (converted to requests.X and limits.X):

  • cpu - Total CPU cores (e.g., "4" or "500m")
  • memory - Total memory (e.g., "4Gi" or "512Mi")
  • ephemeral-storage - Ephemeral storage limit (e.g., "10Gi")
  • storage - Total persistent storage (e.g., "100Gi")

Object count quotas (passed as-is):

  • pods - Maximum number of pods
  • services - Maximum number of services
  • services.loadbalancers - Maximum number of LoadBalancer services
  • services.nodeports - Maximum number of NodePort services
  • configmaps - Maximum number of ConfigMaps
  • secrets - Maximum number of Secrets
  • persistentvolumeclaims - Maximum number of PVCs

Example:

resourceQuotas:
  cpu: 4
  memory: 4Gi
  storage: 10Gi
  services.loadbalancers: "3"
  pods: "50"