Skip to content

Commit d2c3fe5

Browse files
LXD-based development environment
1 parent b9490ab commit d2c3fe5

6 files changed

Lines changed: 2068 additions & 0 deletions

File tree

dev/DEV-GUIDE.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# authd Development Environment
2+
3+
LXD VM for daily authd development. Provides full systemd, D-Bus, SSH, and GDM —
4+
enough to build, test, and exercise real PAM/NSS login flows without touching your
5+
host system. The host source tree is bind-mounted at `/workspace/authd`.
6+
7+
## Prerequisites
8+
9+
- LXD: `sudo snap install lxd && lxd init --auto && sudo usermod -aG lxd "$USER"` (logout/login)
10+
- SSH key: `ls ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519`
11+
- SPICE viewer (only needed for `lxc console --type=vga`): `sudo apt install virt-viewer`
12+
13+
The SSH key is injected into the VM for key-based authentication, VS Code Remote SSH,
14+
and testing PAM login flows over SSH (`ssh user@domain.com@vm-ip`).
15+
16+
## Quick Start
17+
18+
```bash
19+
./dev/dev-env.sh up # Create VM + build authd (~20 min first time)
20+
./dev/dev-env.sh broker google \
21+
--client-id ID --client-secret SEC \
22+
--ssh-suffixes '@gmail.com' # Configure broker
23+
ssh you@gmail.com@$(./dev/dev-env.sh ip) # Test login from host
24+
```
25+
26+
The `up` command provisions the VM, installs all toolchains (Go, Rust, protoc),
27+
and automatically builds + installs authd, PAM, and NSS modules. After `up`
28+
completes, the only manual step is configuring a broker with your IdP credentials.
29+
30+
## Commands
31+
32+
Global flags (before the subcommand, apply to all commands):
33+
`--name NAME` (default: authd-dev), `--release NAME` (default: noble), `--workspace PATH` (default: /workspace/authd).
34+
35+
| Command | Description |
36+
|---------|-------------|
37+
| `up` | Create and provision VM (also restarts a stopped VM) |
38+
| `stop` | Stop VM (preserves state; restart with `up`) |
39+
| `down [--force]` | Stop and delete VM and profile |
40+
| `shell` | Direct shell via `lxc exec` (no PAM, always works) |
41+
| `ssh` | Connect via SSH (goes through PAM — for login testing) |
42+
| `status` | VM status and snapshots |
43+
| `snapshot <name>` / `restore <name>` | Manage snapshots |
44+
| `broker <variant> [opts]` | Configure credentials + install broker; `--rebuild` to recompile without touching credentials; `edit` subaction to open broker.conf |
45+
| `build [component]` | Fast rebuild + install a single component |
46+
| `validate` | Health-check authd stack (socket, PAM, NSS, brokers) |
47+
| `test [args]` | Run tests inside the VM (e.g. `--update-golden`, `--skip-external`) |
48+
| `logs [target]` | Tail logs (authd/google/msentraid/oidc/cloud-init) |
49+
| `exec <cmd>` | Run a command inside the VM (in workspace dir) |
50+
| `ip` | Print VM IP |
51+
52+
## Iterative Development
53+
54+
After the initial `install-authd`, use `build` from the **host**:
55+
56+
```bash
57+
./dev/dev-env.sh build authd # Daemon + proto regen + restart
58+
./dev/dev-env.sh build pam # PAM modules (reconnect SSH to load)
59+
./dev/dev-env.sh build nss # NSS module + ldconfig
60+
./dev/dev-env.sh build broker google # Broker binary + restart
61+
./dev/dev-env.sh build all # Full install-authd
62+
```
63+
64+
To run tests inside the VM:
65+
66+
```bash
67+
./dev/dev-env.sh test # All tests with race detection (default)
68+
./dev/dev-env.sh test ./internal/brokers/... # Specific package
69+
./dev/dev-env.sh test --update-golden # Auto-update golden files
70+
./dev/dev-env.sh test --skip-external # Skip VHS (requires external tools)
71+
```
72+
73+
Tail service logs from the host:
74+
75+
```bash
76+
./dev/dev-env.sh logs authd # authd daemon logs
77+
./dev/dev-env.sh logs google # Google broker logs
78+
./dev/dev-env.sh logs cloud-init # Cloud-init provisioning log
79+
```
80+
81+
## Broker Setup
82+
83+
```bash
84+
# Google (--issuer defaults to accounts.google.com)
85+
./dev/dev-env.sh broker google \
86+
--client-id 843411...googleusercontent.com \
87+
--client-secret GOCSPX-... \
88+
--ssh-suffixes '@gmail.com'
89+
90+
# Microsoft Entra ID
91+
./dev/dev-env.sh broker msentraid \
92+
--issuer https://login.microsoftonline.com/TENANT_ID/v2.0 \
93+
--client-id CLIENT_ID \
94+
--ssh-suffixes '@yourdomain.com'
95+
96+
# Generic OIDC (Keycloak, etc.)
97+
./dev/dev-env.sh broker oidc \
98+
--issuer https://keycloak.example.com/realms/myrealm \
99+
--client-id authd-client --client-secret SECRET \
100+
--ssh-suffixes '*'
101+
```
102+
103+
The `broker` command patches credentials, enables, and restarts the service automatically.
104+
Add `--rebuild` to recompile from source (e.g. after pulling broker changes).
105+
Use `broker <variant> edit` to open `broker.conf` directly in your editor.
106+
107+
## Validation
108+
109+
After installing authd (and optionally a broker), verify the full stack:
110+
111+
```bash
112+
./dev/dev-env.sh validate
113+
```
114+
115+
Checks: toolchain, authd.socket, PAM module, NSS config, broker registration.
116+
117+
## Troubleshooting
118+
119+
| Problem | Check |
120+
|---------|-------|
121+
| VM won't start | `lxc info authd-dev` |
122+
| Provisioning failed | `./dev/dev-env.sh logs cloud-init` or `lxc exec authd-dev -- cloud-init status` |
123+
| Build failed during `up` | `./dev/dev-env.sh exec ./dev/scripts/install-authd` to retry |
124+
| Wrong Go version | `which go` should be `/usr/local/go/bin/go`, not `/usr/bin/go` |
125+
| authd won't start | `sudo systemctl status authd.socket` (authd uses socket activation) |
126+
| SSH login rejects user | `ssh_allowed_suffixes_first_auth` must be set in broker.conf |
127+
| GDM console login password | `cat ~/.config/<vm-name>/vm-password` (default: `~/.config/authd-dev/vm-password`) |
128+
| VGA console freezes immediately | See [Known issue: VGA console on HiDPI](#known-issues) below |
129+
130+
**Recovery from failed provisioning:** `./dev/dev-env.sh down --force && ./dev/dev-env.sh up`
131+
132+
**Re-running install scripts:** `./dev/dev-env.sh exec ./dev/scripts/install-authd` is safe to re-run (idempotent config, rebuilds binaries).
133+
`broker` auto-detects whether a binary exists: if it does, only credentials are patched (no rebuild). Pass `--rebuild` to recompile from source.
134+
135+
**Snapshots:** `up` creates two snapshots: `clean` (toolchain only, pre-build) and `installed` (authd + PAM + NSS built; no broker credentials — run `./dev/dev-env.sh broker <variant>` to configure).
136+
Use `./dev/dev-env.sh restore installed` to reset to a freshly built state.
137+
138+
**Go/Rust versions** are auto-synced from `go.mod` and `authd-oidc-brokers/rust-toolchain.toml`
139+
at VM creation time. No manual version management needed.
140+
141+
## GDM / VGA Console
142+
143+
The VM runs GDM at `graphical.target` so you can test real GDM login flows.
144+
145+
**Accessing the GDM screen:**
146+
```bash
147+
lxc console authd-dev --type=vga # Opens SPICE viewer (requires virt-viewer on host)
148+
```
149+
150+
The default login password is random-generated at VM creation time:
151+
```bash
152+
cat ~/.config/authd-dev/vm-password # default VM name
153+
cat ~/.config/<vm-name>/vm-password # custom --name
154+
```
155+
156+
**Known issues:**
157+
158+
| Issue | Status | Workaround |
159+
|-------|--------|------------|
160+
| **VGA console freezes on HiDPI displays (>1440p)** | [lxd#17606](https://github.com/canonical/lxd/issues/17606) — open bug, Ubuntu 25.10, LXD 6.6 | Lower host resolution to ≤1440p before opening console; or use LXD web UI (`lxc config show authd-dev` → connect on `:8443`) |
161+
| **VGA console drops on VM reboot** | [lxd#16184](https://github.com/canonical/lxd/issues/16184) — open, targeted for LXD 26.04 | Re-run `lxc console authd-dev --type=vga` after reboot |
162+
163+
> **Wayland note:** `remote-viewer` (from `virt-viewer`) runs via XWayland on Wayland hosts.
164+
> The freeze issue is worse on Wayland + HiDPI due to SPICE resolution negotiation.
165+
> Using X11 (`DISPLAY=:0` session) or lowering resolution are the practical
166+
> workarounds until lxd#17606 is resolved.
167+
168+
## File Layout
169+
170+
```
171+
dev/
172+
├── dev-env.sh # VM lifecycle + build (run from host)
173+
├── cloud-init.yaml # VM provisioning template (Go, Rust, deps, GDM)
174+
├── DEV-GUIDE.md # This file
175+
├── lib/
176+
│ └── common.sh # Shared helpers (output, build, variant config)
177+
└── scripts/
178+
├── install-authd # Build + install authd + PAM + NSS + configure (verbose mode)
179+
└── install-broker # Configure or build+install OIDC broker + D-Bus + systemd
180+
```

dev/cloud-init.yaml

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
#cloud-config
2+
# authd Development Environment - Cloud-Init Configuration
3+
#
4+
# Single cloud-init user-data for the LXD VM dev environment.
5+
# Installs all build/test dependencies, Go, Rust, and a minimal GNOME desktop
6+
# for GDM login testing.
7+
#
8+
# Placeholders replaced at launch time by dev-env.sh:
9+
# __SSH_PUBLIC_KEY__ - your SSH public key
10+
# __GO_VERSION__ - Go toolchain version (from go.mod)
11+
# __RUST_CHANNEL__ - Rust toolchain channel (from rust-toolchain.toml)
12+
# __VM_PASSWORD__ - randomly generated VM password
13+
14+
ssh_authorized_keys:
15+
- __SSH_PUBLIC_KEY__
16+
17+
package_update: true
18+
# Intentionally false (unlike e2e cloud-init which uses true) to speed up
19+
# VM provisioning. Dev VMs are short-lived and don't need full upgrades.
20+
package_upgrade: false
21+
22+
packages:
23+
# ── Build dependencies (from debian/control) ──
24+
- protobuf-compiler
25+
- pkgconf
26+
- gcc
27+
- make
28+
- git
29+
- libpam0g-dev
30+
- libglib2.0-dev
31+
- libpwquality-dev
32+
- libc6-dev
33+
- libssl-dev
34+
- systemd-dev
35+
# ── Test dependencies (from .github/workflows/qa.yaml) ──
36+
- bubblewrap
37+
- cracklib-runtime
38+
- dbus
39+
- openssh-server
40+
- uidmap
41+
- openssh-client
42+
- apparmor-profiles
43+
- gdm3
44+
- gnome-shell
45+
- spice-vdagent
46+
# ── Dev ergonomics ──
47+
- shellcheck # Shell script linting (used in CI qa.yaml)
48+
# ── Runtime / Utilities ──
49+
- systemd-timesyncd
50+
- curl
51+
- wget
52+
- jq
53+
- vim
54+
- tree
55+
- ripgrep
56+
- htop
57+
- software-properties-common
58+
59+
write_files:
60+
# SSH config for authd PAM testing (matches e2e-tests/vm/cloud-init-template)
61+
- path: /etc/ssh/sshd_config.d/authd.conf
62+
owner: root:root
63+
permissions: "0644"
64+
content: |
65+
UsePAM yes
66+
Match User *@*
67+
KbdInteractiveAuthentication yes
68+
69+
# PATH setup for Go and Rust toolchains
70+
- path: /etc/profile.d/authd-dev.sh
71+
owner: root:root
72+
permissions: "0644"
73+
content: |
74+
# Go (installed to /usr/local/go from go.dev)
75+
export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH"
76+
# Rust (rustup, installed per-user)
77+
if [ -d "$HOME/.cargo/bin" ]; then
78+
export PATH="$HOME/.cargo/bin:$PATH"
79+
fi
80+
81+
# Suppress login banner noise (from e2e-tests/vm/cloud-init-template)
82+
- path: /etc/skel/.hushlogin
83+
content: ""
84+
85+
# dconf profile for the desktop session
86+
- path: /etc/dconf/profile/user
87+
content: |
88+
user-db:user
89+
system-db:local
90+
91+
# Prevent the desktop from suspending or blanking the screen
92+
# (from e2e-tests/vm/cloud-init-template-questing.yaml)
93+
- path: /etc/dconf/db/local.d/00-authd-dev
94+
content: |
95+
[org/gnome/settings-daemon/plugins/power]
96+
sleep-inactive-ac-type='nothing'
97+
sleep-inactive-ac-timeout=0
98+
99+
[org/gnome/desktop/session]
100+
idle-delay=uint32 0
101+
102+
runcmd:
103+
# --- Configure swap (4GB) to prevent OOM during large Go builds ---
104+
# msgraph-sdk-go/models requires >3GB during compilation; 4GB swap provides insurance.
105+
- |
106+
set -e
107+
echo "Configuring 4GB swap file..."
108+
fallocate -l 4G /swapfile
109+
chmod 600 /swapfile
110+
mkswap /swapfile
111+
swapon /swapfile
112+
echo '/swapfile none swap sw 0 0' >> /etc/fstab
113+
echo 'vm.swappiness=10' >> /etc/sysctl.d/99-swap.conf
114+
sysctl -p /etc/sysctl.d/99-swap.conf 2>/dev/null | grep swappiness || true
115+
echo "Swap configured (4GB, swappiness=10 to prefer memory)"
116+
117+
# --- Install Go from go.dev ---
118+
# System Go is too old for authd; version is injected from go.mod by dev-env.sh.
119+
- |
120+
set -e
121+
GO_VERSION="__GO_VERSION__"
122+
ARCH=$(dpkg --print-architecture)
123+
case "$ARCH" in
124+
amd64) GO_ARCH="amd64" ;;
125+
arm64) GO_ARCH="arm64" ;;
126+
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
127+
esac
128+
echo "Installing Go ${GO_VERSION} (${GO_ARCH})..."
129+
wget -q "https://go.dev/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" -O /tmp/go.tar.gz
130+
# Verify download succeeded and is non-empty
131+
if [ ! -s /tmp/go.tar.gz ]; then
132+
echo "ERROR: Go download failed or is empty"; exit 1
133+
fi
134+
rm -rf /usr/local/go
135+
tar -C /usr/local -xzf /tmp/go.tar.gz
136+
rm -f /tmp/go.tar.gz
137+
echo "Go installed: $(/usr/local/go/bin/go version)"
138+
139+
# --- Install Rust via rustup (for ubuntu user) ---
140+
# RUSTUP_INIT_SKIP_PATH_CHECK: required because dh-cargo may pull in
141+
# system Rust, which causes rustup to error.
142+
# Toolchain channel is injected from rust-toolchain.toml by dev-env.sh.
143+
- |
144+
set -e
145+
su - ubuntu -c 'export RUSTUP_INIT_SKIP_PATH_CHECK=yes && curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain __RUST_CHANNEL__'
146+
su - ubuntu -c '. "$HOME/.cargo/env" && rustup default __RUST_CHANNEL__ && echo "Rust: $(rustc --version), $(cargo --version)"'
147+
148+
# --- Set PATH in ubuntu's .bashrc for non-interactive SSH sessions ---
149+
# /etc/profile.d only applies to login shells. Non-interactive SSH commands
150+
# (ssh vm 'go build ...') need PATH set in .bashrc.
151+
- |
152+
cat >> /home/ubuntu/.bashrc <<'BASHRC'
153+
154+
# authd dev environment PATH (added by cloud-init)
155+
export PATH="/usr/local/go/bin:$HOME/go/bin:$HOME/.cargo/bin:$PATH"
156+
BASHRC
157+
158+
# --- Install Go protobuf tools (mirrors CONTRIBUTING.md method) ---
159+
- |
160+
su - ubuntu -c '
161+
export PATH="/usr/local/go/bin:$HOME/go/bin:$HOME/.cargo/bin:$PATH"
162+
cd /workspace/authd/tools
163+
grep -o "_ \"[^\"]*\"" *.go | cut -d "\"" -f 2 | sort -u | xargs -r go install
164+
'
165+
echo "Go protobuf tools installed"
166+
167+
# --- AppArmor bubblewrap profile (required by tests) ---
168+
- |
169+
if [ -f /usr/share/apparmor/extra-profiles/bwrap-userns-restrict ]; then
170+
ln -sf /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d/
171+
apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict 2>/dev/null || true
172+
echo "AppArmor bubblewrap profile loaded"
173+
fi
174+
175+
# --- System hardening for dev comfort ---
176+
- sed -i 's/^LOGIN_TIMEOUT.*/LOGIN_TIMEOUT 360/' /etc/login.defs
177+
- systemctl mask systemd-networkd-wait-online.service || true
178+
- systemctl mask apt-daily.service apt-daily.timer || true
179+
- systemctl mask apt-daily-upgrade.service apt-daily-upgrade.timer || true
180+
- systemctl mask unattended-upgrades.service || true
181+
- rm -f /etc/apt/apt.conf.d/20auto-upgrades /etc/apt/apt.conf.d/50unattended-upgrades
182+
183+
# --- Desktop configuration ---
184+
# graphical.target enables GDM for desktop login testing.
185+
# SSH still works — GDM does not block SSH access.
186+
- systemctl set-default graphical.target
187+
- dconf update
188+
- sed -i 's/enabled=1/enabled=0/' /etc/default/apport 2>/dev/null || true
189+
- systemctl mask --now apport.service || true
190+
# Password for GDM console login (SSH uses key auth).
191+
# Generated randomly by dev-env.sh at VM creation time.
192+
- echo 'ubuntu:__VM_PASSWORD__' | chpasswd
193+
194+
# --- Restart SSH to pick up authd PAM config ---
195+
- systemctl restart ssh
196+
197+
- echo "=== authd development environment provisioning complete ==="

0 commit comments

Comments
 (0)