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
67 changes: 67 additions & 0 deletions .github/workflows/dev-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Temporary: builds a test image for the feat/lldap-go-sync-sdk branch so the
# dev stack (clients/dev) can pull an image that already has the read-only
# snapshot port. Remove once the read-only port ships in a tagged release.
name: Build LLDAP dev test image
on:
workflow_dispatch:
push:
branches:
- "feat/lldap-go-sync-sdk"

jobs:
publish_dockerhub_amd64:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3

- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASS }}

- name: Build lldap and push Docker image
uses: docker/build-push-action@v3
with:
push: true
tags: beclab/lldap:go-sync-sdk-dev-amd64
file: Dockerfile
platforms: linux/amd64

publish_dockerhub_arm64:
runs-on: self-hosted
steps:
- name: Check out the repo
uses: actions/checkout@v3

- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASS }}

- name: Build lldap and push Docker image
uses: docker/build-push-action@v3
with:
push: true
tags: beclab/lldap:go-sync-sdk-dev-arm64
file: Dockerfile
platforms: linux/arm64

publish_manifest:
needs:
- publish_dockerhub_amd64
- publish_dockerhub_arm64
runs-on: ubuntu-latest
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASS }}

- name: Push manifest
run: |
docker manifest create beclab/lldap:go-sync-sdk-dev --amend beclab/lldap:go-sync-sdk-dev-amd64 --amend beclab/lldap:go-sync-sdk-dev-arm64
docker manifest push beclab/lldap:go-sync-sdk-dev
87 changes: 87 additions & 0 deletions clients/dev/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Local dev stack

A persistent `lldap + nats` stack so an app you're building (e.g. `files`) can
connect to LLDAP and consume account events from its own `docker-compose`, with
data that survives restarts. (For the one-shot smoke test see [../go/test](../go/test).)

## Start

```bash
docker compose pull
docker compose up -d
```

On first boot LLDAP creates one admin user, **`alice`** / `password`. Add more
users with `lldapctl.sh` (below).

| What | Address | Credentials |
| --------------- | ----------------------- | -------------------- |
| Web / GraphQL | http://localhost:17170 | `alice` / `password` |
| Read-only API | http://localhost:17171 | none (network-gated) |
| LDAP | `localhost:3890` | bind as `alice` |
| NATS | `nats://localhost:4222` | `lldap` / `secret` |

Notes:
- The admin is `alice`, not LLDAP's default `admin`: the compose sets
`LLDAP_LDAP_USER_DN: alice`, so LLDAP bootstraps it as the admin (at least one
admin is mandatory — see `server/src/main.rs`).
- The read-only port (`17171`, `GET /readonly/snapshot`) — the endpoint the SDK
consumer reads — works out of the box: the default image is a temporary branch
build (`beclab/lldap:go-sync-sdk-dev`, see `.github/workflows/dev-image.yml`)
that includes it. Use the commented `build:` block only to test un-pushed local
changes, or override the tag with `LLDAP_IMAGE=...`.
- Data lives in the `lldap_pgdata` volume (Postgres, since SQLite rejects the v12
migration). `docker compose down` keeps it; `down -v` wipes it for a clean DB.

Reset to an empty DB (re-creates `alice` on next boot):

```bash
docker compose down -v
docker compose up -d
```

## Writing the consumer

The consumer reads the snapshot from LLDAP's unauthenticated read-only port
(`GET /readonly/snapshot` on `17171`) — no credentials; access is meant to be
gated by the network layer (e.g. a K8s NetworkPolicy). The default image already
exposes it (see Notes above).

Use the single `client` package; [../go/example/main.go](../go/example/main.go)
is the template. Write one idempotent `apply(ctx, client.Changes) error` (return
nil only when the whole batch applied — see the SDK README), call
`c.Init(ctx, dbUsers, dbGroups)` at startup and apply the result, then `c.Run`
with `OnChanges: apply`. `Run` consumes `os.users` / `os.groups` as reconcile
triggers; `User.IsAdmin()` reports `lldap_admin` membership.

Run the reference consumer on your host and watch it reconcile:

```bash
curl -s http://localhost:17171/readonly/snapshot | jq # peek at the payload

cd ../go
LLDAP_URL=http://localhost:17171 \
NATS_URL=nats://localhost:4222 NATS_USERNAME=lldap NATS_PASSWORD=secret \
go run ./example
```

## CRUD helper: `lldapctl.sh`

Drives user/group changes via GraphQL and, after each mutation, publishes the
matching `os.users` / `os.groups` NATS trigger — **simulating app-service** (in
Olares app-service emits these; LLDAP itself does not). Needs only `curl`, `jq`,
and `docker`; the publish runs in a one-shot `nats-box` container and is
best-effort.

```bash
./lldapctl.sh create-user carol # email/displayName auto-filled from the id
./lldapctl.sh make-admin carol
./lldapctl.sh remove-admin carol
./lldapctl.sh list-users
./lldapctl.sh delete-user carol
```

Env overrides: `LLDAP_URL`, `ADMIN_USER`, `ADMIN_PASS`, `EMAIL_DOMAIN`,
`NATS_NETWORK`, `NATS_SERVER`, `NATS_USERNAME`, `NATS_PASSWORD`, `NATS_BOX_IMAGE`.
Run `./lldapctl.sh help` for all commands. It can't set passwords (the image has
no set-password binary), but consumers only read, so test users don't need one.
96 changes: 96 additions & 0 deletions clients/dev/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Persistent local dev stack: lldap + nats (JetStream).
#
# Unlike clients/go/test (an ephemeral smoke-test stack torn down with
# `down -v`), this stack is meant to stay up so a separately developed app
# (e.g. files) can connect from its own compose via the shared `lldap-dev`
# network, and so its data survives restarts.
#
# docker compose pull && docker compose up -d

services:
nats:
image: nats:2.10
command: ["-js", "--user", "lldap", "--pass", "secret"]
ports:
- "4222:4222"
- "8222:8222"
networks:
- lldap-dev
restart: unless-stopped

# Postgres instead of SQLite: the repo's v12 migration adds an auto-increment
# column via ALTER TABLE, which SQLite rejects ("near AUTOINCREMENT").
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: lldap
POSTGRES_PASSWORD: lldap
POSTGRES_DB: lldap
healthcheck:
test: ["CMD-SHELL", "pg_isready -U lldap -d lldap"]
interval: 2s
timeout: 3s
retries: 30
volumes:
- lldap_pgdata:/var/lib/postgresql/data
networks:
- lldap-dev
restart: unless-stopped

lldap:
# This is a mock LLDAP for other apps to develop against, so pull a published
# image instead of compiling Rust locally. The default is a temporary branch
# build (CI: .github/workflows/dev-image.yml) that already has the read-only
# snapshot port (17171); switch back to a released tag once the port ships in
# a real release. Override LLDAP_IMAGE to pin another tag.
image: ${LLDAP_IMAGE:-beclab/lldap:go-sync-sdk-dev}
# To test unreleased changes from this checkout instead, comment out the
# `image:` line above and uncomment this build block:
# build:
# context: ../.. # repo root, to include this branch's NATS wiring
# dockerfile: Dockerfile
depends_on:
postgres:
condition: service_healthy
nats:
condition: service_started
environment:
LLDAP_JWT_SECRET: dev_jwt_secret_please_change
LLDAP_KEY_SEED: dev_key_seed
LLDAP_LDAP_BASE_DN: dc=example,dc=com
# Bootstrap admin: LLDAP creates this user on first boot (when lldap_admin
# has no members), so we make it `alice` instead of the default `admin` to
# avoid a separate service account. See server/src/main.rs.
LLDAP_LDAP_USER_DN: alice
LLDAP_LDAP_USER_EMAIL: alice@example.com
LLDAP_LDAP_USER_PASS: password
LLDAP_DATABASE_URL: "postgres://lldap:lldap@postgres/lldap"
LLDAP_VERBOSE: "true"
# Unauthenticated read-only snapshot port (GET /readonly/snapshot); this is
# the endpoint the Go SDK consumes. Access control is left to the network
# layer. The default `go-sync-sdk-dev` image already has this port; an older
# released image would ignore it (use the `build:` block above instead).
LLDAP_HTTP_READONLY_PORT: "17171"
# avatar is a base64 JpegPhoto blob, useless for identity sync; keep it
# out of the snapshot. The denylist matches the stored attribute name.
LLDAP_HTTP_READONLY_DENY_ATTRIBUTES: avatar
NATS_HOST: nats
NATS_PORT: "4222"
NATS_USERNAME: lldap
NATS_PASSWORD: secret
ports:
- "17170:17170"
- "17171:17171"
- "3890:3890"
networks:
- lldap-dev
restart: unless-stopped

volumes:
lldap_pgdata:

# This compose owns the network and gives it a stable name; other compose
# projects join it with `external: true`.
networks:
lldap-dev:
name: lldap-dev
Loading
Loading