diff --git a/CHANGELOG.md b/CHANGELOG.md index aaaaa55..d5136d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ orchestration code; see each submodule's own `CHANGELOG.md` [sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md)) for what changed inside the apps it composes. +## [v2.0.1] - 2026-08-09 + +Rolls up **sso-manager-node v2.0.1** and **theta-agent v2.0.1**. + +### Added +- **Non-Root Secrets Group Access**: Set up `theta-secrets` and `theta` system groups in `setup.sh` and set permission flags to `0750` on `/etc/theta42` and `0640` on `agent.yml` to allow non-root users in the group to request secrets. +- **Autostart Tray Icon Companion**: Configured `/etc/xdg/autostart/theta-agent-tray.desktop` in `setup.sh` to automatically launch the desktop tray icon companion. +- **OpenBao / OpenBoa Resource Exclusion**: Skipped internal secret/renewer services from populating the directory resources catalogue. +- **Full Docker Integration Tests**: Rewrote the integration test runner (`test-integration.sh`) to support full env-mode LDAP seeding (`seed-test-user.sh`) and pass test environment configs/parameters reliably inside Docker containers. + ## [v2.0.0] - 2026-08-09 Rolls up **sso-manager-node v2.0.0**, **theta-agent v2.0.0**, **jump-host v2.0.0**. diff --git a/bootstrap/bootstrap.js b/bootstrap/bootstrap.js index f22af25..7552d7c 100644 --- a/bootstrap/bootstrap.js +++ b/bootstrap/bootstrap.js @@ -645,7 +645,7 @@ async function seedDirectory(token, clientId, jumpClientId) { // Remove or set ignored on OpenBao/bao-renewer seed resources if present — OpenBao is an // internal stack service, not a user-facing published directory service. for (const r of resources) { - if (r.slug === 'openbao' || r.slug === 'bao-renewer' || (r.name && r.name.includes('openbao')) || (r.name && r.name.includes('bao-renewer'))) { + if (r.slug === 'openbao' || r.slug === 'openboa' || r.slug === 'bao-renewer' || (r.name && (/openbao|openboa|bao-renewer/i.test(r.name)))) { await dirDelete(token, `resources/${r.id}`).catch(() => {}); } } diff --git a/setup.sh b/setup.sh index 9b18bb8..c12fb8c 100755 --- a/setup.sh +++ b/setup.sh @@ -1307,12 +1307,13 @@ if [[ "$CFG_THETA_AGENT_ENABLE" == "1" ]]; then warn "No agent join key available — /etc/theta42/agent.yml has no credential and the agent will not connect." fi # We want to connect to either https or http depending on CFG_CREATE_ALL_HTTP - if [[ "${CFG_CREATE_ALL_HTTP:-0}" == "1" ]]; then - sudo sed -i "s|https://sso.example.com|http://${SSO_HOST}|" /etc/theta42/agent.yml - else - sudo sed -i "s|https://sso.example.com|https://${SSO_HOST}|" /etc/theta42/agent.yml - fi - sudo chmod 600 /etc/theta42/agent.yml + sudo getent group theta-secrets >/dev/null 2>&1 || sudo groupadd -r theta-secrets 2>/dev/null || true + sudo getent group theta >/dev/null 2>&1 || sudo groupadd -r theta 2>/dev/null || true + SECRETS_GRP="root" + if getent group theta-secrets >/dev/null 2>&1; then SECRETS_GRP="theta-secrets"; elif getent group theta >/dev/null 2>&1; then SECRETS_GRP="theta"; fi + sudo chown -R "root:$SECRETS_GRP" /etc/theta42 2>/dev/null || true + sudo chmod 750 /etc/theta42 + sudo chmod 640 /etc/theta42/agent.yml fi # Stop a running agent before overwriting its binary (cp into a # running executable fails with "Text file busy" on a re-install). @@ -1320,6 +1321,27 @@ if [[ "$CFG_THETA_AGENT_ENABLE" == "1" ]]; then sudo cp theta-agent-linux-amd64 /usr/local/bin/theta-agent sudo chmod +x /usr/local/bin/theta-agent + # Install desktop tray companion if available + TRAY_SRC="dist/theta-agent-tray-linux-amd64" + if [[ ! -f "$TRAY_SRC" ]] && [[ -f "theta-agent-tray-linux-amd64" ]]; then TRAY_SRC="theta-agent-tray-linux-amd64"; fi + if [[ -f "$TRAY_SRC" ]]; then + sudo cp "$TRAY_SRC" /usr/local/bin/theta-agent-tray + sudo chmod +x /usr/local/bin/theta-agent-tray + sudo mkdir -p /etc/xdg/autostart + sudo bash -c "cat <<'EOF' > /etc/xdg/autostart/theta-agent-tray.desktop +[Desktop Entry] +Type=Application +Name=Theta Agent Tray +Comment=Theta Agent Desktop Tray Companion +Exec=/usr/local/bin/theta-agent-tray +Icon=network-workgroup +Terminal=false +Categories=Utility;System; +X-GNOME-Autostart-enabled=true +EOF" + info " theta-agent-tray companion installed." + fi + sudo bash -c "cat <<'EOF' > /etc/systemd/system/theta-agent.service [Unit] Description=Theta Agent diff --git a/sso-manager-node b/sso-manager-node index bb744ad..e8824cb 160000 --- a/sso-manager-node +++ b/sso-manager-node @@ -1 +1 @@ -Subproject commit bb744adb5e6b965d3cb8301c1f9b2449687991d8 +Subproject commit e8824cb28d4cabef45ec5e3b34e8b42e3bc4d82c diff --git a/test-integration.sh b/test-integration.sh index 44b71cd..005e60b 100755 --- a/test-integration.sh +++ b/test-integration.sh @@ -1,44 +1,227 @@ -#!/bin/bash -set -e +#!/usr/bin/env bash +# test-integration.sh — Full Docker integration test for theta-suite. +# +# Starts the sso-manager container in test mode (no secrets.js required), +# seeds an LDAP test user, runs the full jest suite inside the container, +# then tears everything down. +# +# Usage: +# ./test-integration.sh # run all tests +# ./test-integration.sh --no-build # skip docker build (reuse existing image) +# ./test-integration.sh --keep # leave containers up after tests (for debugging) -echo "=== Starting theta-suite Integration Tests ===" +set -euo pipefail -echo "=> Cleaning up any existing containers and volumes..." -docker compose down -v +# ── Colours ────────────────────────────────────────────────────────────────── +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' +info() { echo -e "${CYAN}[test]${NC} $*"; } +ok() { echo -e "${GREEN}[✓]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +fail() { echo -e "${RED}[✗]${NC} $*"; exit 1; } -echo "=> Running setup.sh to initialize environment..." -# Run setup non-interactively if possible (we might need to export some env vars) -# setup.sh uses dialog, which requires a terminal, but it falls back to defaults if not interactive? -# Actually setup.sh has a dialog UI. Let's just run it or provide a seeded config. -# If setup.sh is strictly interactive, we might need to bypass it or provide answers. -# Let's try running docker compose up directly if setup.sh is too interactive, but the user explicitly said "Make sure setup.sh like your change, then do a full release. Make sure each repo has a current change log, is pushed and and merged." and "Automated testing in theta-suite to test integration between all the include projects". +# ── Options ─────────────────────────────────────────────────────────────────── +NO_BUILD=0; KEEP=0 +for arg in "$@"; do + case "$arg" in + --no-build) NO_BUILD=1 ;; + --keep) KEEP=1 ;; + --help|-h) echo "Usage: $0 [--no-build] [--keep]"; exit 0 ;; + *) warn "Unknown option: $arg" ;; + esac +done -# Wait, setup.sh has no silent mode out of the box unless we provide answers. -echo "=> Initializing OpenBao manually for tests (simulating setup.sh)" -# Actually, setup.sh initializes Vault. If we don't run it, Vault is sealed! -# Let's just write a curl test that checks if the containers start. +# ── Test environment config (self-contained, no secrets.js needed) ──────────── +export COMPOSE_PROJECT_NAME="theta-test" +TEST_CONTAINER="theta-test-sso-manager-1" -docker compose up -d +LDAP_BASE_DN="dc=test,dc=local" +LDAP_ADMIN_PASS="testadminpass" +TEST_UID="test" +TEST_PASSWORD="MyTestPassword!2" # must match tests/setup.js TEST_CREDS -echo "=> Waiting for services to become healthy..." -sleep 15 # Give time for containers to spin up +# ── Cleanup on exit ─────────────────────────────────────────────────────────── +cleanup() { + local exit_code=$? + if [[ "$KEEP" == "1" ]]; then + warn "Leaving containers up (--keep). Tear down with: docker compose -p theta-test down -v" + else + info "Tearing down test stack..." + docker compose -p theta-test -f docker-compose.test.yml down -v --remove-orphans 2>/dev/null || true + fi + exit $exit_code +} +trap cleanup EXIT INT TERM -# Test proxy -echo "=> Testing Proxy..." -if ! curl -sS -o /dev/null -w "%{http_code}" http://localhost | grep -q "406"; then - echo "❌ Proxy failed to respond with 406 Not Acceptable on port 80 (default behavior)" - exit 1 +# ── Write a minimal test compose override ──────────────────────────────────── +info "Writing docker-compose.test.yml..." +cat > docker-compose.test.yml <<'COMPOSEEOF' +# Minimal test stack: sso-manager only (no proxy, no openbao, no jump-host). +# Uses env-mode config — no secrets.js or openbao token required. +services: + sso-manager: + build: + context: ./sso-manager-node + dockerfile: Dockerfile.openldap + target: "" + container_name: theta-test-sso-manager + restart: "no" + networks: [theta-test-net] + environment: + - NODE_ENV=test + - NODE_PORT=3001 + - LDAP_BASE_DN=dc=test,dc=local + - LDAP_ADMIN_PASS=testadminpass + - ORG_NAME=Test Org + - LDAP_DOMAIN=test.local + # Inline JWT secret for tests (no secrets.js or bao needed) + - app_oauth__jwtSecret=test-integration-jwt-secret-theta42 + ports: + - "13001:3001" + - "10389:389" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/health"] + interval: 5s + timeout: 5s + retries: 24 + start_period: 30s +networks: + theta-test-net: + driver: bridge +COMPOSEEOF + +# ── Build ───────────────────────────────────────────────────────────────────── +if [[ "$NO_BUILD" == "0" ]]; then + info "Building sso-manager test image..." + docker compose -p theta-test -f docker-compose.test.yml build sso-manager + ok "Image built" +else + warn "Skipping build (--no-build)" fi -echo "✅ Proxy responds on port 80" -# Test SSO Manager Node -echo "=> Testing SSO Manager..." -if ! curl -sS -f -o /dev/null http://localhost:3001; then - echo "❌ SSO Manager failed to respond on port 3001" - exit 1 +# ── Start ───────────────────────────────────────────────────────────────────── +info "Starting sso-manager container..." +docker compose -p theta-test -f docker-compose.test.yml up -d sso-manager + +# ── Wait for healthy ────────────────────────────────────────────────────────── +info "Waiting for sso-manager to become healthy (up to 120s)..." +for i in $(seq 1 120); do + STATUS=$(docker inspect --format='{{.State.Health.Status}}' theta-test-sso-manager 2>/dev/null || echo "missing") + if [[ "$STATUS" == "healthy" ]]; then + ok "sso-manager is healthy" + break + fi + if [[ $i -eq 120 ]]; then + warn "Container never became healthy. Logs:" + docker logs theta-test-sso-manager --tail 60 + fail "sso-manager failed to become healthy after 120s" + fi + sleep 1 +done + +# ── Wait for LDAP ───────────────────────────────────────────────────────────── +info "Waiting for LDAP on port 10389..." +for i in $(seq 1 30); do + if ldapsearch -x -H ldap://localhost:10389 -b "" -s base "(objectClass=*)" >/dev/null 2>&1; then + ok "LDAP is ready" + break + fi + if [[ $i -eq 30 ]]; then + fail "LDAP did not become reachable on localhost:10389 after 30s" + fi + sleep 1 +done + +# ── Seed test user ──────────────────────────────────────────────────────────── +info "Seeding test LDAP user via seed-test-user.sh..." + +docker cp sso-manager-node/test/seed-test-user.sh theta-test-sso-manager:/tmp/seed-test-user.sh +docker exec \ + -e LDAP_HOST=localhost \ + -e LDAP_PORT=389 \ + -e BIND_DN="cn=admin,${LDAP_BASE_DN}" \ + -e BIND_PW="${LDAP_ADMIN_PASS}" \ + -e BASE_DN="${LDAP_BASE_DN}" \ + theta-test-sso-manager \ + sh /tmp/seed-test-user.sh + +ok "Test user seeded" + +# ── Install dev deps (jest) inside the running container ────────────────────── +info "Installing test dependencies (jest) inside container..." +docker exec theta-test-sso-manager sh -c " + cd /app && + if ! command -v jest >/dev/null 2>&1 && [ ! -f node_modules/.bin/jest ]; then + npm install --save-dev jest@latest supertest@latest --silent 2>&1 | tail -3 + else + echo 'jest already installed' + fi +" +ok "Test deps ready" + +# ── Copy test files into container ──────────────────────────────────────────── +info "Copying tests into container..." +docker cp sso-manager-node/nodejs/tests/. theta-test-sso-manager:/app/tests/ + +ok "Tests copied" + +# ── Run jest ────────────────────────────────────────────────────────────────── +info "Running full jest test suite inside container..." +echo "" + +# These app_* vars are set by the entrypoint for the main process but NOT +# inherited by docker exec subprocesses. Pass them explicitly so the jest +# process loads app.js with the correct LDAP connection details. +docker exec \ + -e NODE_ENV=test \ + -e REDIS_URL="redis://127.0.0.1:6379" \ + -e app_oauth__jwtSecret="test-integration-jwt-secret-theta42" \ + -e app_ldap__url="ldap://localhost:389" \ + -e app_ldap__bindDN="cn=admin,${LDAP_BASE_DN}" \ + -e app_ldap__bindPassword="${LDAP_ADMIN_PASS}" \ + -e app_ldap__userBase="ou=people,${LDAP_BASE_DN}" \ + -e app_ldap__groupBase="ou=groups,${LDAP_BASE_DN}" \ + theta-test-sso-manager \ + sh -c " + cd /app + # Write test conf with full LDAP connection details so jest workers get + # the correct config without needing to inherit docker exec env vars. + # app_* env vars are only applied at conf-module require time, but jest + # workers may not reliably inherit them across all parallelism models. + cat > /app/conf/test.js << CONFEOF +'use strict'; +module.exports = { + redis: { prefix: 'sso_manager_test_' }, + oauth: { jwtSecret: 'test-integration-jwt-secret-theta42' }, + ldap: { + url: 'ldap://localhost:389', + bindDN: 'cn=admin,${LDAP_BASE_DN}', + bindPassword: '${LDAP_ADMIN_PASS}', + userBase: 'ou=people,${LDAP_BASE_DN}', + groupBase: 'ou=groups,${LDAP_BASE_DN}' + } +}; +CONFEOF + echo 'conf/test.js written' + REDIS_URL='redis://127.0.0.1:6379' node_modules/.bin/jest --forceExit --passWithNoTests 2>&1 + " +JEST_EXIT=$? + +echo "" +if [[ $JEST_EXIT -eq 0 ]]; then + ok "All jest tests passed!" +else + fail "Some jest tests failed (exit code $JEST_EXIT)" fi -echo "✅ SSO Manager responds on port 3001" -echo "=== All integration tests passed! ===" -docker compose down -v -exit 0 +# ── Theta-agent Go tests (host-side, no docker needed) ─────────────────────── +if command -v go >/dev/null 2>&1 && [[ -d theta-agent ]]; then + info "Running theta-agent Go tests..." + (cd theta-agent && go test ./... -count=1 2>&1) + ok "Theta-agent Go tests passed" +else + warn "Skipping theta-agent Go tests (go not found or theta-agent dir missing)" +fi + +ok "All integration tests complete!" diff --git a/theta-agent b/theta-agent index a3a21c3..7758784 160000 --- a/theta-agent +++ b/theta-agent @@ -1 +1 @@ -Subproject commit a3a21c38844f6b673eb6d52552db9b3759432d6b +Subproject commit 775878437e078ccd5d1b13f469cdd0e1633a7d11