# Quick Start: Run Nauthera on kind

> Install the published Nauthera Helm chart on a local kind cluster, check its discovery document, sign in, and register your own client with an OIDCClient.

This guide installs the published Helm chart with the server and its operator, then
creates an account, registers a client with an `OIDCClient` manifest and opens the admin
dashboard. It needs no source checkout and no registry credentials: the chart and both
images are public on GHCR. Run it on a local kind cluster with a throwaway PostgreSQL,
or on a cluster you already run.

## Prerequisites

- `kubectl` and Helm 3 or 4, `curl` and `jq`
- On a laptop: Docker and [kind](https://kind.sigs.k8s.io/)
- On your own cluster: what [that section](#on-a-cluster-you-already-run) lists

## 1. Start the stack

On a laptop, follow [On kind](#on-kind). If you already run a cluster, skip to
[On a cluster you already run](#on-a-cluster-you-already-run).

### On kind

Create a cluster that publishes the server's NodePort on `https://localhost:8443`:

```yaml
# kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: nauthera
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30443
        hostPort: 8443
        listenAddress: "127.0.0.1"
```

```bash
kind create cluster --config kind.yaml
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager -n cert-manager \
  --create-namespace --set crds.enabled=true --wait
kubectl create namespace nauthera
```

Then a PostgreSQL whose data lives in an `emptyDir`, and the Secret the chart reads its
connection from (`nauthera-pg-owner`, key `uri`):

```yaml
# postgres.yaml — throwaway: the data is gone with the pod
apiVersion: v1
kind: Secret
metadata:
  name: nauthera-pg-owner
  namespace: nauthera
stringData:
  password: local-only
  uri: postgres://nauthera:local-only@postgres.nauthera.svc:5432/nauthera?sslmode=disable
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: nauthera
spec:
  selector:
    matchLabels: { app: postgres }
  template:
    metadata:
      labels: { app: postgres }
    spec:
      containers:
        - name: postgres
          image: postgres:17
          env:
            - { name: POSTGRES_USER, value: nauthera }
            - { name: POSTGRES_DB, value: nauthera }
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef: { name: nauthera-pg-owner, key: password }
          ports:
            - containerPort: 5432
          volumeMounts:
            - { name: data, mountPath: /var/lib/postgresql/data }
      volumes:
        - name: data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: nauthera
spec:
  selector: { app: postgres }
  ports:
    - port: 5432
```

```bash
kubectl apply -f postgres.yaml
kubectl -n nauthera rollout status deploy/postgres
```

Install the chart with its `operator.yaml` profile: one replica that migrates the
database on start, no Redis, TLS from a CA the chart creates, self-registration on, and
the operator talking to the server over mutual TLS. The profile is written for images
loaded into kind (`pullPolicy: Never`), so let the kubelet pull the released ones, which
the chart pins by digest:

```bash
VERSION=<version>   # the newest is at the top of https://www.nauthera.io/downloads
helm pull oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" --untar
helm install nauthera oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" \
  -n nauthera -f nauthera-server/profiles/operator.yaml \
  --set image.pullPolicy=IfNotPresent --set operator.image.pullPolicy=IfNotPresent

kubectl -n nauthera rollout status deploy/nauthera-nauthera-server
```

| Service | URL | Notes |
|---|---|---|
| nauthera-server | `https://localhost:8443` | Public OAuth 2.0 / OIDC surface and sign-in UI |
| admin dashboard | `https://localhost:8443/admin/` | For an account that holds an admin role; step 5 grants one |

The certificate comes from the chart's own CA, which nothing trusts yet. Save it, so
`curl` and your applications can trust it, and add it to your browser or click through
the warning:

```bash
kubectl -n nauthera get secret nauthera-nauthera-server-ca \
  -o jsonpath='{.data.tls\.crt}' | base64 -d > nauthera-local-ca.crt
```

The chart's install notes (`helm get notes nauthera -n nauthera`) show how to add it to
the macOS and Debian trust stores, and how to remove it again.

### On a cluster you already run

Nauthera needs a few things beside it that this guide does not install, because most
clusters already have their own way of running them:

| Dependency | What Nauthera needs | We suggest |
|---|---|---|
| cert-manager | Issues the token signing keys, the control plane's mutual TLS and, with an issuer of yours, the public certificate | [cert-manager](https://cert-manager.io/) with a `ClusterIssuer`, such as Let's Encrypt |
| PostgreSQL | The only database; a Secret with its connection URI | [CloudNativePG](https://cloudnative-pg.io/): its `Cluster` publishes the `<cluster>-app` Secret with a `uri` key |
| Redis | Required for any issuer that is not `localhost`; must not evict keys (`maxmemory-policy noeviction`) | The [OT-Container-Kit Redis operator](https://github.com/OT-CONTAINER-KIT/redis-operator), with a password Secret |
| Ingress or Gateway | Routes the public hostname to the server | The ingress controller or Gateway API implementation you already run |

Setting these up is out of scope here; [Installation](https://www.nauthera.io/docs/v1/installation) and
[Operational requirements](https://www.nauthera.io/docs/v1/adoption/operational-requirements) say what each
must provide in production. With them in place, create the namespace and the two
encryption keys the server needs on an issuer that is not `localhost`:

```bash
kubectl create namespace nauthera
kubectl -n nauthera create secret generic nauthera-keys \
  --from-literal=mfa-encryption-key="$(openssl rand -hex 32)" \
  --from-literal=keys-encryption-key="$(openssl rand -hex 32)"
```

Back up `nauthera-keys`: without it, the MFA secrets and every organization's sealed keys
cannot be read again. Then write the values that name your hostname and the Secrets.
They go on top of the chart's production profile:

```yaml
# my-values.yaml
ingress:
  host: auth.example.com            # becomes the issuer: https://auth.example.com
  className: nginx                  # your ingress class
certManager:
  issuerRef:
    name: letsencrypt               # your ClusterIssuer, for the public certificate
    kind: ClusterIssuer
  grpcTLS:
    enabled: true                   # the operator calls the control plane over mutual TLS
operator:
  enabled: true
  mainOrganizationNamespaces: [nauthera]
config:
  cache:
    address: nauthera-redis:6379    # your Redis Service
    tls: false                      # true when your Redis serves TLS
  server:
    grpc:
      enabled: true
  auth:
    registration:
      enabled: true                 # for this guide's step 3; turn it off afterwards
secrets:
  databaseDsn:
    existingSecret: nauthera-pg-app # CloudNativePG's <cluster>-app Secret
    existingSecretKey: uri
  cachePassword:
    existingSecret: nauthera-redis  # the Secret holding your Redis password
    existingSecretKey: password
  mfaEncryptionKey:
    existingSecret: nauthera-keys
  keysEncryptionKey:
    existingSecret: nauthera-keys
```

Behind a Gateway instead of an ingress, replace the `ingress` block:

```yaml
ingress:
  enabled: false
httpRoute:
  enabled: true
  parentRefs:
    - name: public                  # your Gateway
      namespace: gateway
  hostnames: [auth.example.com]
config:
  issuer:
    url: https://auth.example.com
```

The production profile runs three replicas, trusts proxies in the private address
ranges, and turns on a NetworkPolicy, a ServiceMonitor and alerting rules; the
ServiceMonitor and rules need the Prometheus Operator's CRDs, so set
`metrics.serviceMonitor.enabled` and `metrics.prometheusRule.enabled` to `false` without
them. Install:

```bash
VERSION=<version>   # the newest is at the top of https://www.nauthera.io/downloads
helm pull oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" --untar
helm install nauthera oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" \
  -n nauthera -f nauthera-server/profiles/production.yaml -f my-values.yaml

kubectl -n nauthera rollout status deploy/nauthera-nauthera-server
```

In the steps below, use your issuer, `https://auth.example.com`, wherever they say
`https://localhost:8443`, and leave out `--cacert nauthera-local-ca.crt`: your
`ClusterIssuer` serves a certificate your machine already trusts. For step 5, add
`-f my-values.yaml` to the `helm upgrade`, or keep `--reuse-values`.

## 2. Check the issuer

```bash
curl -s --cacert nauthera-local-ca.crt https://localhost:8443/.well-known/openid-configuration \
  | jq '{issuer, grant_types_supported}'
```

The issuer is `https://localhost:8443`. The advertised grant types are
`authorization_code`, `refresh_token`, `client_credentials` and
`urn:ietf:params:oauth:grant-type:device_code`.

## 3. Create an account

Open `https://localhost:8443/ui/register` and register. Registration asks for the
built-in proof-of-work captcha, which the browser solves on its own. You then land on
the hosted sign-in pages, signed in to the main organization.

## 4. Register your own client

The operator registers each `OIDCClient` with the server and writes the generated
secret into a Secret beside it. The profile lets manifests in the `nauthera` namespace
configure the main organization:

```yaml
# my-app.yaml
apiVersion: nauthera.io/v1alpha1
kind: OIDCClient
metadata:
  name: my-app
  namespace: nauthera
spec:
  clientId: my-app
  displayName: My App
  redirectUris:
    - http://localhost:5173/callback
  grantTypes:
    - authorization_code
    - refresh_token
  responseTypes:
    - code
  scopes:
    - openid
    - profile
    - email
    - offline_access
  tokenEndpointAuthMethod: client_secret_basic
  pkcePolicy: required_s256
  allowRefresh: true
  secret:
    generate: true
```

```bash
kubectl apply -f my-app.yaml
kubectl -n nauthera get oidcclients            # wait for Ready
kubectl -n nauthera get secret my-app -o jsonpath='{.data.client-secret}' | base64 -d
```

The Secret `my-app` holds `client-id`, `client-secret` and `issuer`; mount it into the
application, or read it as above. Re-applying the manifest never rotates the secret.
For a public client, set `tokenEndpointAuthMethod: none` and leave out `secret`. Then
point any OpenID Connect library at the issuer `https://localhost:8443`, trusting
`nauthera-local-ca.crt`. [OIDCClient](https://www.nauthera.io/docs/v1/oidc-client) has every field.

## 5. Open the admin dashboard

The dashboard admits an account that holds an admin role, and the server grants one to
an account that already exists and is named in `auth.admin.subjects`. Name the account
you registered in step 3:

```bash
helm upgrade nauthera oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" \
  -n nauthera --reuse-values --set 'config.auth.admin.subjects={you@example.com}'
kubectl -n nauthera rollout status deploy/nauthera-nauthera-server
```

The new pod grants that account `platform-owner`, and `https://localhost:8443/admin/`
opens for it ([The first administrator](https://www.nauthera.io/docs/v1/user-management#the-first-administrator)).

## 6. Clean up

On kind, delete the cluster:

```bash
kind delete cluster --name nauthera
```

On your own cluster, turn self-registration off again
(`config.auth.registration.enabled: false`) or remove the release with
`helm uninstall nauthera -n nauthera`. The resource definitions stay, so the
`OIDCClient`s you declared are kept.

## Next steps

- [Grafana sign-in](https://www.nauthera.io/integrations/grafana): an application signing in through
  Nauthera, with its `OIDCClient` and the Secret the operator writes.
- [Installation](https://www.nauthera.io/docs/v1/installation): the production profile, with Redis,
  replicas, an ingress and cert-manager issuing from your own CA.
- [Clients](https://www.nauthera.io/docs/v1/clients): every way to register an application.
- [Organizations](https://www.nauthera.io/docs/v1/organizations): add tenants with their own issuers.
