Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions docs/admins/install/certificates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
---
title: Certificates and DNS
description: The four hostnames every installation needs, why one of them must be a wildcard, and the two ways to get a certificate for it.
---

# Certificates and DNS

Read this before you install. The wildcard requirement below is the single thing
most likely to delay a new installation, because it often needs a request to a
DNS or PKI team that takes days.

## Four hostnames per installation

For an installation whose landing page is at `eduide.example.edu`:

| Hostname | Serves |
|---|---|
| `eduide.example.edu` | the landing page |
| `service.eduide.example.edu` | the REST service the landing page calls |
| `instance.eduide.example.edu` | session ingress |
| `*.webview.instance.eduide.example.edu` | **per-session webviews** |

All four need DNS pointing at your Gateway's external address. The fourth is a
**wildcard record**, because every session gets its own subdomain under it.

:::caution Check your DNS policy first
Some institutions do not hand out wildcard records, or require a specific
approval. Find out before you plan the rest of the install — there is no
configuration that avoids the wildcard if you want webviews.
:::

## Why the wildcard exists

Inside the IDE, anything rendered in a panel — a Markdown preview, a notebook, a
rendered PDF, embedded documentation — is served from its own origin so that it
cannot script against the IDE itself. Those origins are
`<something>.webview.instance.<your host>`.

Without the wildcard, sessions still start and the IDE still loads. **Only the
previews break**, with an opaque failure inside the IDE. That means this is
usually discovered by a student weeks after go-live rather than by you during
installation.

## Why ACME cannot issue it over HTTP-01

This is the part that catches people out.

cert-manager will happily issue certificates for the first three hostnames using
an **HTTP-01** challenge: it serves a token over plain HTTP on port 80 at that
exact hostname, and the CA fetches it.

That cannot work for a wildcard. To prove control of `*.webview.instance.<host>`
you would have to serve a token at every possible name under it, which is
infinite. **The ACME specification therefore does not permit HTTP-01 for
wildcards at all** — it is not a cert-manager limitation and no configuration
changes it.

You have two options.

## Option A — DNS-01 (recommended)

With a **DNS-01** challenge, cert-manager proves control by writing a TXT record
into your zone, which works for a wildcard because it proves control of the
whole zone. This is the better answer: cert-manager then issues *and renews* the
wildcard automatically, and you never think about it again.

It needs an API credential for your DNS zone. cert-manager has built-in support
for Route53, CloudDNS, AzureDNS, Cloudflare, DigitalOcean, ACME-DNS and RFC-2136
dynamic updates, plus community webhook providers for many university DNS
systems.

**Ask your DNS team for a scoped API credential before assuming this is
impossible.** RFC-2136 works with BIND, which many universities run.

Store the credential as a Secret, then set `gatewayAcmeIssuer.solvers` in the
cluster chart values:

```yaml
gatewayAcmeIssuer:
enabled: true
email: platform@example.edu
solvers:
# DNS-01 for the wildcard
- dns01:
cloudflare:
apiTokenSecretRef: { name: cloudflare-api-token, key: token }
selector:
dnsNames: ["*.webview.instance.eduide.example.edu"]
# HTTP-01 for everything else
- http01:
gatewayHTTPRoute:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: theia-shared-gateway
namespace: eduide-system
```

Then simply list the wildcard alongside the other names:

```yaml
managedCertificates:
enabled: true
certificates:
- name: eduide-tls
secretName: eduide-tls
dnsNames:
- eduide.example.edu
- service.eduide.example.edu
- instance.eduide.example.edu
- "*.webview.instance.eduide.example.edu"
```

## Option B — bring your own wildcard certificate

If you cannot get a DNS-01 credential, obtain the wildcard some other way — a
commercial CA, or your institution's certificate service — and hand it to the
cluster chart.

```bash
cat wildcard.crt | base64 | tr -d '\n' # -> certificate
cat wildcard.key | base64 | tr -d '\n' # -> key
```

```yaml
wildcardTLSSecret:
create: true
name: eduide-webview-tls
certificate: "<base64 of the full chain>"
key: "<base64 of the private key>"
```

and point the webview listener at it:

```yaml
gateway:
listeners:
- name: prod-webview
hostname: "*.webview.instance.eduide.example.edu"
tlsSecretName: eduide-webview-tls
```

The trade-off is that **this does not renew itself.** Put the expiry date in
whatever your team uses to track such things. A wildcard that expires takes out
every webview at once.

## Nothing tells you when a certificate is wrong

This is worth internalising, because it has already cost one installation six
months.

Gateway API **never compares a certificate's names against the listener's
hostname.** A listener whose Secret holds a certificate for an entirely
different host reports:

```
Programmed=True Accepted=True ResolvedRefs=True
```

Everything looks healthy. The first symptom is a browser certificate warning.
The second is subtler and much more confusing: the landing page loads (the user
clicks through the warning), then its JavaScript calls `service.<host>` — a
different origin — and the browser **silently blocks that request** because that
certificate is invalid too. The launch never reaches the server, so there is
nothing in any log to find.

**Verify explicitly, and never with `curl -k`:**

```bash
for h in eduide.example.edu service.eduide.example.edu instance.eduide.example.edu; do
echo | openssl s_client -connect "$h:443" -servername "$h" 2>/dev/null \
| openssl x509 -noout -checkhost "$h"
done
```

Each should print `Host <name> matches certificate`. Then check the wildcard by
testing a name under it:

```bash
h=probe.webview.instance.eduide.example.edu
echo | openssl s_client -connect "$h:443" -servername "$h" 2>/dev/null \
| openssl x509 -noout -checkhost "$h"
```

And confirm a browser would accept it — `%{ssl_verify_result}` must be `0`:

```bash
curl -s -o /dev/null -w '%{http_code} %{ssl_verify_result}\n' https://eduide.example.edu
```

## Adding an installation later

When you add a second installation to a cluster, its three non-wildcard names
must be added to the certificate, and it needs its own webview wildcard.

A certificate that covers your existing installations but not the new one will
not fail anything visibly — see above. Re-check with the commands in this page
after every change.

:::tip Only put names with a listener on a certificate
cert-manager proves each name with its own challenge. A name on the certificate
that has no matching Gateway listener will fail its HTTP-01 challenge with a
404, and that one pending challenge **blocks the whole certificate** — including
every name that would otherwise have worked.
:::

## Checklist

- [ ] Four DNS records per installation, one of them a wildcard
- [ ] DNS policy permits wildcards, or Option B is agreed
- [ ] A DNS-01 credential obtained, or a wildcard certificate obtained and its
expiry tracked
- [ ] Every hostname verified with `openssl ... -checkhost`, not `curl -k`
- [ ] `ssl_verify_result` is `0` for the landing page **and** the service host
169 changes: 169 additions & 0 deletions docs/admins/install/prerequisites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
title: Cluster Prerequisites
description: Everything that must exist on the cluster before EduIDE is installed, with the commands to put it there.
---

# Cluster Prerequisites

EduIDE does not install its own platform layer. Work through this page first;
`helm install` will otherwise fail, or — worse — succeed and not work.

Everything here is installed **once per cluster**, not once per installation.

## What you need before you start

| | |
|---|---|
| **Kubernetes** | 1.26 or later. Gateway API `v1` and the CRD conversion webhooks both need it |
| **Cluster-admin** | You will install CRDs, ClusterRoles and ClusterIssuers |
| **A LoadBalancer** | Something must give the Gateway an external address — a cloud provider's controller, MetalLB, or equivalent |
| **DNS you can change** | Four records per installation, **one of them a wildcard**. See [Certificates and DNS](certificates.md) — check early that your DNS team permits wildcards |
| **An OIDC provider** | Keycloak is what EduIDE is tested against |

## 1. Gateway API CRDs

EduIDE routes every session through Gateway API. The CRDs are not part of
Kubernetes and must be installed separately.

```bash
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml
```

Pin the version deliberately rather than tracking latest — a Gateway API major
version bump is a breaking change.

```bash
kubectl get crd gateways.gateway.networking.k8s.io # should exist
```

## 2. A Gateway controller

The CRDs above are just types; something has to implement them. EduIDE is
developed and tested against **Envoy Gateway**.

```bash
helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.2.1 -n envoy-gateway-system --create-namespace
kubectl -n envoy-gateway-system rollout status deploy/envoy-gateway
```

Any conformant controller should work, but nothing else has been tried. If you
use another, expect to adjust `gateway.className` in the cluster chart.

```bash
kubectl get gatewayclass # you should see one, and it should be Accepted
```

## 3. cert-manager — with Gateway API support turned on

```bash
helm repo add jetstack https://charts.jetstack.io && helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--version v1.16.2 \
--set crds.enabled=true \
--set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \
--set config.kind="ControllerConfiguration" \
--set config.enableGatewayAPI=true
```

:::warning `enableGatewayAPI` is not optional
Without it, cert-manager ignores Gateway resources entirely and your HTTP-01
challenges never complete. If you installed cert-manager earlier without this
flag, upgrade with it and restart the controller:

```bash
kubectl -n cert-manager rollout restart deploy/cert-manager
```
:::

cert-manager also provides the CA injection that EduIDE's CRD conversion webhook
depends on, so it is required even if you bring your own certificates.

```bash
kubectl -n cert-manager get pods # controller, webhook and cainjector all Running
```

## 4. A storage class

Every session that is not ephemeral gets a PersistentVolumeClaim.

```bash
kubectl get storageclass
```

Requirements:

- **ReadWriteOnce** is sufficient; sessions do not share volumes.
- **Dynamic provisioning** is required — volumes are created on demand as
students start sessions.
- Volume expansion is not required but is useful.

Note the name; you will set it as `operator.storageClassName`. Do not rely on
the cluster's default class being the one you want.

:::caution More than one default class
If two storage classes are both marked default, a PVC that omits the class gets
an arbitrary one, and you will get inconsistent behaviour that is hard to
attribute. Check with:

```bash
kubectl get storageclass -o custom-columns=NAME:.metadata.name,DEFAULT:.metadata.annotations.'storageclass\.kubernetes\.io/is-default-class'
```
:::

## 5. Node disk for preloaded images

EduIDE preloads every IDE image onto **every node**, so a session starts in
seconds rather than waiting on a multi-gigabyte pull.

Budget accordingly: each IDE image is roughly 0.5–1 GB, and the default set is
eight. Allow **at least 20 GB** of image storage per node, and more if you add
languages. Trim the app list in the tenant values if that is too much — see
[Applications](../platform/app-definitions.md).

## 6. Prometheus Operator — only if you want metrics

EduIDE ships PodMonitors and Grafana dashboards, and they are **off by default**
because they are not portable: they need the Prometheus Operator CRDs, and the
namespaces they target differ per monitoring stack.

```bash
kubectl get crd podmonitors.monitoring.coreos.com
```

If that is absent, leave `monitoring.enabled: false`. Everything else works
without it.

## 7. An OIDC provider

EduIDE authenticates through OIDC, tested against Keycloak. You need:

- A realm your students can authenticate to.
- A **public** client — EduIDE's landing page is a browser application and holds
no secret.
- Redirect URIs for **all four** of the installation's hostnames. Three is a
common mistake and breaks webviews specifically.

See [Access Control](../platform/access-control.md) for the full client setup.

You can defer this: an installation can be brought up with
`keycloak.allowUnauthenticated: true` to prove the platform works before wiring
identity. **That installation has no authentication and must not be exposed.**

## Checklist

Before installing EduIDE:

- [ ] Kubernetes 1.26+, cluster-admin
- [ ] Gateway API CRDs installed, pinned
- [ ] A Gateway controller running, GatewayClass `Accepted`
- [ ] cert-manager running, **with `enableGatewayAPI=true`**
- [ ] A storage class chosen, with dynamic provisioning
- [ ] ~20 GB of image space per node
- [ ] DNS you can change, **wildcards permitted**
- [ ] A certificate plan — read [Certificates and DNS](certificates.md) before
going further, because the wildcard requirement surprises people
- [ ] An OIDC realm and public client, or a deliberate decision to defer

Then continue to [Installing EduIDE](installing.md).
Loading
Loading