Skip to content

Latest commit

 

History

History
322 lines (257 loc) · 18.6 KB

DEPLOYING.md

File metadata and controls

322 lines (257 loc) · 18.6 KB

Deploying Directory Console

How to run the console as a lasting service: in a container on a server with Docker, or on Kubernetes (both recommended), or as a plain Java program without a container (possible, but discouraged; section 10), and signing people in either with their eDirectory password or through an OpenID Connect provider (single sign-on). QUICKSTART.md is the short version for trying it out on your own machine.

  1. Plan
  2. Run it on a server with Docker
  3. Run it on Kubernetes
  4. Set it up
  5. Sign-in without an authorization server
  6. Sign-in with an authorization server (single sign-on)
  7. The console's certificate
  8. Behind a reverse proxy or load balancer
  9. Check the deployment
  10. Without a container (not recommended)

1. Plan

  • One instance. The console keeps its settings, sessions, trust store and audit log in one data volume and in memory, so run exactly one copy. It cannot be scaled out or run active-active; for availability, rely on the container restarting and back up the volume (see UPGRADING.md).
  • Its address. Choose the host name people will use, such as https://console.example.com. It becomes CONSOLE_PUBLIC_URL. It must be exactly what browsers see: the console checks it on every change, puts it in the single sign-on redirect URI, and puts its host in the certificate it makes.
  • HTTPS only. The console serves HTTPS itself, on port 8443 (CONSOLE_PORT; inside the container, when it runs in one). There is no plain HTTP port.
  • Network. Inbound: HTTPS from people's browsers (or from your proxy). Outbound: LDAPS (normally 636) to every eDirectory server it works with, and the connections listed in UPGRADING.md; with single sign-on, HTTPS to the provider (its certificate from a public CA, or from a CA you add to the console's trust store).
  • Accounts you need. A tree administrator for setup (it becomes the break-glass account), and a service account for finding people by short name and for single sign-on (section 5).
  • Size. Small: 1 CPU and 1 GB of memory are plenty; the volume needs well under 1 GB (the audit log grows slowly, one file a month).

2. Run it on a server with Docker

On a Linux server with Docker Engine and the compose plugin, make a folder (for example /opt/directory-console) and download compose.yaml from the latest release into it. Change two things in it: the port browsers use, and the public address.

services:
  console:
    image: ${CONSOLE_IMAGE:-ghcr.io/pointbluetechnology/directory-console:latest}
    restart: unless-stopped
    ports:
      - "443:8443"
    environment:
      CONSOLE_PUBLIC_URL: https://console.example.com
    # the rest as released: the data volume, the read-only root, no capabilities

Leave the hardening lines as released (read_only, tmpfs, cap_drop, no-new-privileges). To choose when you upgrade, pin a version instead of latest (see UPGRADING.md). If your network needs a proxy for outbound HTTPS, add HTTPS_PROXY under environment:.

Start it, and read the setup link from the log:

docker compose up -d
docker compose logs console

restart: unless-stopped brings the console back after a reboot as long as Docker starts at boot (systemctl enable docker on most distributions). Then go to Set it up.

3. Run it on Kubernetes

Use version 0.3.10 or later on Kubernetes: 0.3.9 and earlier stop at start on a volume owned by root (as the local-path provisioner makes them), and answer probes sent by IP address with 421.

container/kubernetes.yaml has a namespace, a volume claim, a deployment of one pod, a service, and an ingress for ingress-nginx to uncomment. It was tried on k3s 1.36 with the local-path storage class and ingress-nginx. Edit CONSOLE_PUBLIC_URL, and set the image tag to a version rather than latest (a pinned version upgrades only when you change it; see UPGRADING.md). Then:

kubectl apply -f kubernetes.yaml
kubectl -n directory-console logs deploy/directory-console

The log shows the setup link. What the manifest does, and why:

  • One replica, Recreate updates. Never two pods at once: they would share, and fight over, one volume.
  • A ReadWriteOnce volume claim for /data. Your cluster's default storage class provides it; set storageClassName to choose another. When the volume is owned by root and fsGroup does not apply to it, the log says so once: the console leaves /data's own permissions as they are, and everything it writes inside is owner-only.
  • The same hardening as the container: non-root user 65532 (fsGroup 65532 so the volume is writable), a read-only root file system with /tmp in memory, no capabilities, no privilege escalation, the runtime's default seccomp profile.
  • Probes on /api/v2/health/live, over HTTPS. Not /api/v2/health/ready for readiness: that answers 503 until setup is finished, which would keep the setup page itself out of reach.
  • A ClusterIP service on port 443. Reach it through an ingress or a LoadBalancer service; see section 8. To try it before that: kubectl -n directory-console port-forward svc/directory-console 8443:443 and use https://localhost:8443 (with CONSOLE_PUBLIC_URL set to that address while you try).

The admin commands run in the pod:

kubectl -n directory-console exec deploy/directory-console -- java -jar /app/console.jar admin verify-audit

To back up, copy the volume's contents with your usual tool (a volume snapshot, or Velero), or download a backup from Settings > History and backup.

4. Set it up

Open the setup link from the log. It shows the console certificate's fingerprint, as the log does: check they match. Enter one of your eDirectory servers and sign in as a tree administrator. That tree becomes the console's first directory, and that administrator its break-glass account, who can always sign in with a password.

Then, in Settings > Directory, choose the Tier: Production (the default) asks for the directory's name to be typed before every change; Lab does not.

5. Sign-in without an authorization server

People sign in with their eDirectory name and password, and act with their own eDirectory rights.

  1. Service account (Settings > Directory). Without one, people must type their full name (DN), such as cn=jsmith,ou=staff,o=acme. With one, they type jsmith. Give it an account that can read the people who will sign in (browse and read cn), and nothing more.
  2. Search bases: the containers where short names are looked for. None means the whole tree.
  3. When a name matches several people: let them choose their account, or ask for the full name.
  4. Role mappings (Settings > Role mappings): map eDirectory groups to the console's roles (viewer, editor, operator, admin), optionally limited to containers. Someone with no role cannot use the console, whatever their eDirectory rights.
  5. Sessions (Settings > Directory): idle sign-out and the longest session.

6. Sign-in with an authorization server (single sign-on)

The provider (Entra ID, Okta, Keycloak, OpenText Access Manager or any OpenID Connect provider) says who the person is. The console finds their eDirectory account through the service account, and then acts as them with eDirectory's proxied authorization, so they still have only their own rights, in Identity Manager too. Accounts that are disabled, expired or locked by intruder detection cannot sign in this way. Password sign-in stays available alongside it, unless you limit it to the break-glass and listed accounts (step 7 below).

At the provider, register an application (a "web" or "confidential" client):

  • Redirect URI: https://console.example.com/api/v2/oidc/callback, your CONSOLE_PUBLIC_URL followed by /api/v2/oidc/callback. Settings > Single sign-on shows the exact value to register.
  • Flow: authorization code. Either keep a client secret, or make it a public client (PKCE, no secret).
  • Claims: a claim that names the eDirectory account, such as preferred_username or email; and, to map provider groups to console roles, a groups claim in the ID token.
  • Note the issuer URL (its /.well-known/openid-configuration must be reachable from the console), the client id, and the secret.

The service account finds people for single sign-on, and the console acts as them through it. In eDirectory, give it the Supervisor right over the people who may sign in this way (for example on their container), and no broader rights. Proxied authorization needs that right.

In the console, Settings > Single sign-on:

  1. Issuer, Client id, Client secret (or Public client), Scopes (openid profile email unless your provider needs others), and a Button label for the sign-in page ("Sign in with …").
  2. Claim and Matches the attribute: for example the claim preferred_username matches cn, or email matches mail. Exactly one account must match.
  3. Under these containers: only people there can sign in this way. Keep it to the containers the service account has Supervisor over.
  4. Never act as: accounts that must never be signed in through the provider, such as the tree administrator and other administrators. They sign in with a password.
  5. Groups claim and Provider groups: map the provider's groups to console roles, as role mappings do for eDirectory groups. A dotted path such as realm_access.roles works for nested claims.
  6. Try the mapping: enter a claim value and see which account it finds. Then sign in with the button. Check its rights (at the top of the page, and under Settings > Directory) reads the service account's own rights on a few people under each container: whether it can find them and act as them, and whether it has more than it needs (Supervisor over the whole tree, or over a break-glass account).
  7. Password sign-in (optional): choose Single sign-on only, except the accounts below to stop everyone else signing in with a password. The break-glass accounts always may; list any other accounts that should (for example administrators who need a way in when the provider is down), one DN per line. They keep the roles their groups give them. The sign-in page then offers single sign-on first, with a link to the password form. While this is on, single sign-on and the service account cannot be removed.

The provider's certificate must come from a public CA, or from a CA the console trusts: add your internal CA under Settings > Trusted certificates (for every directory, or for this one). It takes effect at the next sign-in, without a restart. Each directory has its own provider settings: sign in to a directory to set up its single sign-on.

7. The console's certificate

On first start the console makes a self-signed certificate for CONSOLE_PUBLIC_URL's host and prints its fingerprint. Browsers warn about it until you replace it, in Settings > Console certificate, in one of two ways:

  • Upload a certificate from your own CA: a PKCS#12 file with its password, or PEM (the certificate with its intermediate CAs, and the private key, encrypted or not). It must name the host in CONSOLE_PUBLIC_URL, unless a reverse proxy in front connects to the console by another name (a box on the form allows that). It is installed at once, without a restart; renew it by uploading again before it expires (the expiry check warns).
  • From an ACME CA (Let's Encrypt, or an internal one such as step-ca), proven with DNS-01: the console adds a TXT record through an RFC 2136 dynamic update signed with a TSIG key, and renews by itself. While this is on, upload is not offered.

Behind a reverse proxy that holds the public certificate, the console's own certificate only needs to satisfy the proxy (next section).

8. Behind a reverse proxy or load balancer

  • The proxy connects to the console over HTTPS (port 8443, or 443 on the Kubernetes service). Either pass TLS through untouched (the console's certificate is what browsers see), or end TLS at the proxy and open a new HTTPS connection to the console; then have the proxy trust the console's certificate, or skip verifying it on that hop if your policy allows.
  • CONSOLE_PUBLIC_URL is the proxy's address, the one browsers use. The console refuses changes from any other origin, and its session cookie needs HTTPS end to end.
  • Pass the Host header through unchanged, and set X-Forwarded-For.
  • Forward /mcp too, with its Authorization header, if people will use AI agents (agent keys, from 0.4.0). A proxy that forwards only the page and /api leaves agents unable to connect.
  • Settings > Reverse proxy: list the proxy's addresses (IPs or CIDR ranges). Only then does the console believe its X-Forwarded-For, so audit entries and sign-in limits see each person's own address.
  • Kubernetes with ingress-nginx: either nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" (TLS ends at the ingress) or nginx.ingress.kubernetes.io/ssl-passthrough: "true" (the controller must be started with --enable-ssl-passthrough). List the controller pods' addresses (or the pod network's range) as trusted proxies.

9. Check the deployment

  • https://console.example.com/api/v2/health/live answers {"version":…,"status":"live"}, and /api/v2/health/ready answers ready once setup is finished.
  • The certificate browsers see is the one you expect.
  • https://console.example.com/mcp answers 401 with WWW-Authenticate: Bearer to a request without an agent key (a 404 means the proxy does not forward it).
  • A person with a role signs in with a short name; one without a role is refused.
  • With single sign-on: the button signs someone in as their own account, and an account under Never act as is refused. With password sign-in limited to single sign-on, an unlisted account's password is refused, and a break-glass account still signs in.
  • The audit log (Audit log page, Check the chain) shows those sign-ins.
  • A backup downloads from Settings > History and backup, and you have stored it and its passphrase apart.

10. Without a container (not recommended)

The console can run as an ordinary Java program, without Docker or Kubernetes. We strongly advise against it. The container is the supported install: these steps work, but you take on what the image does for you.

  • Every update is manual. Each release you stop the console, replace its files by hand and start it again; docker compose pull is not there to do it, and going back means keeping the previous files yourself.
  • Java is yours to keep patched. The image carries a current Java 21 runtime; here you install and update it.
  • The hardening is yours to rebuild. The image runs as a non-root user with a read-only file system and no capabilities; outside it, that is up to the service manager (an example follows).
  • The update banner and the docs assume the image. Their upgrade steps are for compose and Kubernetes.

What you need

  • Linux with Java 21 (a JRE is enough; the image uses Java 21, and that is what is tested).
  • The console's files: console.jar and the lib/ folder beside it. They are in the published image under /app; on any machine with Docker, copy them out, for example for 0.3.16:
docker create --name dc-files ghcr.io/pointbluetechnology/directory-console:0.3.16
docker cp dc-files:/app ./directory-console-0.3.16 && docker rm dc-files

Or build them from source (see the README): container/build.sh --artifacts leaves them in container/artifacts/.

  • A user for the service, and a data folder only it can read (chmod 700), such as /var/lib/directory-console.

Run it

Put the files in a folder of their own, such as /opt/directory-console/0.3.16, and point /opt/directory-console/current at it, so an update can switch folders and a rollback can switch back. Then run, as the service user:

CONSOLE_DATA=/var/lib/directory-console CONSOLE_PORT=8443 CONSOLE_PUBLIC_URL=https://console.example.com:8443 java -XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError -cp "/opt/directory-console/current/console.jar:/opt/directory-console/current/lib/*" com.pointblue.console.server.Main

The setup link and the certificate's fingerprint are printed when it starts, as in a container (with systemd: journalctl -u directory-console). The admin commands run the same way, with the same CONSOLE_DATA:

CONSOLE_DATA=/var/lib/directory-console java -jar /opt/directory-console/current/console.jar admin verify-audit

A systemd unit that keeps some of the container's limits:

[Unit]
Description=Directory Console
After=network-online.target
Wants=network-online.target

[Service]
User=dconsole
Environment=CONSOLE_DATA=/var/lib/directory-console
Environment=CONSOLE_PORT=8443
Environment=CONSOLE_PUBLIC_URL=https://console.example.com:8443
# the path to your Java 21
ExecStart=/usr/bin/java -XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError -cp "/opt/directory-console/current/console.jar:/opt/directory-console/current/lib/*" com.pointblue.console.server.Main
Restart=on-failure
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/directory-console
CapabilityBoundingSet=

[Install]
WantedBy=multi-user.target

Port 8443 needs no privileges. For 443, put a reverse proxy in front (section 8), or forward the port in the firewall, rather than running Java as root.

Updating, and going back

  1. Back up the data folder: stop the service and copy it whole (cp -a /var/lib/directory-console …), or download a backup from Settings > History and backup first. Read the release's notes in UPGRADING.md.
  2. Put the new release's files in a new folder; do not copy them over the old ones (the lib/ jars change names between releases).
  3. Point current at the new folder and restart the service.

To go back: point current at the previous folder, and restore the data backup if the release notes say the configuration format changed.