跳到內容
開源

Registering clients

Before an application can sign people in through Zeabur Identity, you register it as a client. Each client is an OAuthClient object in Zeabur Identity's namespace, and its name is the application's client_id. kubectl is a client too; Get started registered it.

zeabur-identityctl client create builds the object for you: it checks your input, prints the object, and generates a secret when the client needs one. Pipe its output into kubectl apply. The commands below assume the cluster from Get started (namespace zeabur-identity) and run with the cluster admin's kubeconfig.

First decide which kind of client the application is:

TypeForAuthenticates with
publickubectl, desktop and browser-only apps: anything that runs on the user's deviceits client_id and PKCE
confidentialweb apps with a server side (Grafana, Argo CD, Headlamp, …)a secret, plus PKCE

This is the kubectl client from Get started:

zeabur-identityctl client create kubectl --type public \
--redirect-uri http://127.0.0.1:8000/ --audience kubernetes | kubectl apply -f -

Running client create for a name that already exists replaces that client, so always pass all of its options. Leaving out --audience kubernetes here, for example, stops kubectl from getting tokens that kube-apiserver accepts.

The options:

  • --redirect-uri is where the browser goes back to after sign-in. Repeat it for several.
  • --audience kubernetes lets the client get tokens that kube-apiserver accepts. Add it only when the application calls the Kubernetes API as the signed-in person.
  • --home-url is where people open the application, such as https://zeabur.example.com/. With it, the sign-in page offers "Open display name" when someone reaches it with nothing to sign in to, and after they set up a passkey. Anyone who can load the sign-in page sees these names and addresses. https:// only, written as for redirect URIs.

A redirect URI is one of:

  • https:// with a host name, such as https://app.example.com/callback;
  • http://127.0.0.1/… or http://[::1]/…, for apps that receive the result on the user's own machine. Any port matches, so kubectl still works when it has to listen on another port.

Zeabur Identity compares redirect URIs exactly, character by character. To prevent near-misses, zeabur-identityctl accepts a URI only in the form a browser sends it:

  • lowercase scheme and host, and no default port (:443);
  • at least a / path: https://app.example.com/, not https://app.example.com;
  • non-ASCII written as xn-- host names and percent-encoded paths.

It rejects fragments (#…), user names in the URI, plain http to any other host, and addresses a browser would refuse, such as 999.1.1.1 or port 99999.

zeabur-identityctl client create grafana --type confidential --display-name Grafana \
--redirect-uri https://grafana.example.com/login/generic_oauth | kubectl apply -f -

The command prints the client secret in your terminal (on stderr), separately from the object. The secret never ends up in the object, in kubectl's input or in Git. It is shown only once: the cluster keeps just its SHA-256 hash.

Give the secret to the application right away, for example as a Secret in the application's namespace. Paste it in as below rather than passing it as a command argument, which other users on the machine can see in the process list:

read -rs CLIENT_SECRET # paste the secret, then Enter
# monitoring: the namespace Grafana runs in.
printf %s "$CLIENT_SECRET" | kubectl -n monitoring create secret generic grafana-oidc \
--from-file=client-secret=/dev/stdin
unset CLIENT_SECRET

Then configure the application with three values: the issuer URL, the client_id (here grafana) and the secret. It finds everything else through <issuer>/.well-known/openid-configuration. Zeabur Identity accepts the secret either way applications send it: HTTP Basic (client_secret_basic) or a form field (client_secret_post).

Always let zeabur-identityctl client create generate the secret. The hash is visible to anyone who can read OAuthClient objects or your Git repository, and it has no salt. That is safe for a long random secret, but a password someone chose can be guessed back from its hash offline, and Zeabur Identity would never notice.

Without zeabur-identityctl, generate 32 random bytes and hash them yourself:

CLIENT_SECRET=$(openssl rand -hex 32)
printf 'sha256:%s\n' "$(printf %s "$CLIENT_SECRET" | sha256sum | cut -d' ' -f1)" # secretHash

Run the same zeabur-identityctl client create command again and apply its output. The new hash replaces the old one, and the old secret stops working right away, so update the application's Secret in the same step.

Edit the object like any other (kubectl -n zeabur-identity edit oauthclient grafana), or run zeabur-identityctl client create with the new options and apply it. That also replaces the secret of a confidential client.

kubectl -n zeabur-identity get oauthclients # short name: zbclient
kubectl -n zeabur-identity delete oauthclient grafana # nobody can sign in to it any more

The printed object contains no secret, so you can commit it. Convert it to YAML if you prefer:

zeabur-identityctl client create grafana --type confidential --display-name Grafana \
--redirect-uri https://grafana.example.com/login/generic_oauth \
| kubectl create --dry-run=client -o yaml -f - > grafana-client.yaml

Each run generates a new secret, so give the one it prints to the application before you apply the file. Use kubectl create --dry-run=client, not kubectl apply --dry-run=client: apply adds a last-applied-configuration annotation, and, if the client already exists, its resourceVersion, which makes the file fail to apply later.

If you edit the object by hand, the cluster checks it when you apply it: a plain http or fragment redirect URI, or a malformed secretHash, fails at kubectl apply rather than at sign-in. The cluster's URL check is looser than zeabur-identityctl's, though, so run new redirect URIs through zeabur-identityctl client create to catch the rest.