Single-VM E2E Test Results
text
> Run at `2026-03-30 13:06:16 UTC`
============================================================================
Lamaste End-to-End Test Suite
============================================================================
BASE_URL: https://127.0.0.1:9292
SKIP_DNS_TESTS: 1
Date: 2026-03-30 13:06:17 UTC
Running: 01-fresh-install.sh
============================================================================
Lamaste E2E: 01 — Fresh Install
============================================================================
--- Node.js installation ---
[PASS] Node.js installed: v20.20.0
--- Panel server service ---
[PASS] lamaste-panel service is active
--- Health endpoint ---
[PASS] Health endpoint returns status: ok
[PASS] Health endpoint returns a version
--- Panel client static files ---
[PASS] Panel client served at / (HTTP 200)
[PASS] Response contains HTML content
[PASS] Response contains React root element
--- nginx service ---
[PASS] nginx service is active
[PASS] nginx configuration syntax is valid
============================================================================
Results: 9 passed, 0 failed, 0 skipped (9 total)
============================================================================
Running: 02-mtls-enforcement.sh
============================================================================
Lamaste E2E: 02 — mTLS Enforcement
============================================================================
--- Request without client certificate ---
[PASS] Request without cert rejected (HTTP 400)
--- Request with valid client certificate ---
[PASS] Request with valid cert returns HTTP 200
[PASS] Health endpoint returns ok with valid cert
--- Request with invalid certificate ---
[PASS] Request with untrusted cert rejected (HTTP 400)
--- Certificate validity check ---
[PASS] Client certificate has valid expiry: notAfter=Mar 29 13:04:54 2028 GMT
[PASS] Client certificate is signed by the CA
============================================================================
Results: 6 passed, 0 failed, 0 skipped (6 total)
============================================================================
Running: 03-onboarding-flow.sh
============================================================================
Lamaste E2E: 03 — Onboarding Flow
============================================================================
--- Initial onboarding status ---
[INFO] Current onboarding status: COMPLETED
[INFO] Onboarding already completed — testing post-completion behavior
[PASS] POST /onboarding/domain returns 410 after completion
[PASS] POST /onboarding/verify-dns returns 410 after completion
[PASS] POST /onboarding/provision returns 410 after completion
[PASS] GET /onboarding/status still returns 200
============================================================================
Results: 4 passed, 0 failed, 0 skipped (4 total)
============================================================================
Running: 04-tunnel-lifecycle.sh
============================================================================
Lamaste E2E: 04 — Tunnel Lifecycle
============================================================================
--- Pre-flight: check onboarding is complete ---
--- Create tunnel ---
[PASS] Tunnel creation returned ok: true
[PASS] Tunnel subdomain matches
[PASS] Tunnel port matches
[PASS] Tunnel has an ID
[PASS] Tunnel has an FQDN
[PASS] Tunnel has a createdAt timestamp
[INFO] Created tunnel ID: 27bd6d45-1509-4eaa-9c5a-291050c779c1
--- Verify tunnel in list ---
[PASS] Tunnel appears in GET /api/tunnels
--- Verify nginx configuration ---
[PASS] Nginx vhost exists at /etc/nginx/sites-enabled/lamaste-app-e2etest-1774875977
[PASS] nginx -t passes after tunnel creation
--- Validation: reserved subdomain ---
[PASS] Reserved subdomain 'panel' rejected (HTTP 400)
--- Validation: duplicate subdomain ---
[PASS] Duplicate subdomain rejected (HTTP 400)
--- Validation: duplicate port ---
[PASS] Duplicate port rejected (HTTP 400)
--- Validation: invalid port ---
[PASS] Port below 1024 rejected (HTTP 400)
--- Mac plist endpoint ---
[PASS] Mac plist endpoint returns plist content
--- Disable tunnel ---
[PASS] Tunnel disable returned ok: true
[PASS] Tunnel shows as disabled in list
[PASS] Nginx sites-enabled symlink removed for disabled tunnel
[PASS] nginx -t passes after tunnel disable
[PASS] Disabled tunnel excluded from plist
--- Re-enable tunnel ---
[PASS] Tunnel re-enable returned ok: true
[PASS] Tunnel shows as enabled in list
[PASS] Nginx vhost restored for re-enabled tunnel
[PASS] nginx -t passes after tunnel re-enable
[PASS] Re-enabled tunnel included in plist
--- Toggle nonexistent tunnel ---
[PASS] Toggle nonexistent tunnel returns 404
--- Delete tunnel ---
[PASS] Tunnel deletion returned ok: true
[PASS] Tunnel no longer in list after deletion
[PASS] Nginx vhost removed after tunnel deletion
[PASS] nginx -t passes after tunnel deletion
--- Delete nonexistent tunnel ---
[PASS] Delete nonexistent tunnel returns 404
============================================================================
Results: 30 passed, 0 failed, 0 skipped (30 total)
============================================================================
Running: 05-user-lifecycle.sh
============================================================================
Lamaste E2E: 05 — User Lifecycle
============================================================================
--- Pre-flight: check onboarding is complete ---
--- Create user ---
[PASS] User creation returned ok: true
[PASS] Username matches
[PASS] Display name matches
[PASS] Email matches
--- Verify user in list ---
[PASS] User appears in GET /api/users
[PASS] No password field in user list response
[PASS] No bcrypt hash in user list response
--- Validation: duplicate username ---
[PASS] Duplicate username rejected (HTTP 409)
--- Validation: invalid input ---
[PASS] Incomplete user data rejected (HTTP 400)
[PASS] Short password rejected (HTTP 400)
--- Reset TOTP ---
[PASS] TOTP reset returned ok: true
[PASS] TOTP URI is a valid otpauth:// URI
--- TOTP for nonexistent user ---
[PASS] TOTP reset for nonexistent user returns 404
--- Update user ---
[PASS] User update returned ok: true
[PASS] Display name updated
[PASS] Display name persisted after update
--- Update nonexistent user ---
[PASS] Update nonexistent user returns 404
--- Delete user ---
[PASS] User deletion returned ok: true
[PASS] User no longer in list after deletion
--- Cannot delete last user ---
[INFO] Cannot test last-user protection — 2 users exist (need exactly 1)
[INFO] This scenario is tested when only the admin user remains
--- Delete nonexistent user ---
[PASS] Delete nonexistent user returns 404
============================================================================
Results: 20 passed, 0 failed, 0 skipped (20 total)
============================================================================
Running: 06-service-control.sh
============================================================================
Lamaste E2E: 06 — Service Control
============================================================================
--- Pre-flight: check onboarding is complete ---
--- List services ---
[PASS] GET /api/services returns 4 services
[PASS] Service 'nginx' is in the service list
[PASS] Service 'chisel' is in the service list
[PASS] Service 'authelia' is in the service list
[PASS] Service 'lamaste-panel' is in the service list
[PASS] nginx status is 'active'
--- Restart nginx ---
[PASS] nginx restart request accepted
[PASS] nginx is active after restart
--- Reload nginx ---
[PASS] nginx reload returned ok: true
--- Cannot stop lamaste-panel ---
[PASS] Cannot stop lamaste-panel (HTTP 400)
[PASS] Error message explains why panel cannot be stopped
--- Restart lamaste-panel is allowed ---
[PASS] lamaste-panel restart request accepted
[PASS] Panel is responsive after restart
--- Invalid service name ---
[PASS] Unknown service rejected (HTTP 400)
--- Invalid action ---
[PASS] Invalid action 'destroy' rejected (HTTP 400)
============================================================================
Results: 15 passed, 0 failed, 0 skipped (15 total)
============================================================================
Running: 07-cert-renewal.sh
============================================================================
Lamaste E2E: 07 — Certificate Renewal
============================================================================
--- Pre-flight: check onboarding is complete ---
--- List certificates ---
[PASS] GET /api/certs returns 6 certificates
[PASS] Certificate has a type field
[PASS] Certificate has a domain field
[PASS] Certificate has an expiresAt field
[PASS] Certificate has numeric daysUntilExpiry: 89
--- Force renew certificate ---
[SKIP] Certificate renewal requires real Let's Encrypt — skipping
--- Renew nonexistent certificate ---
[SKIP] Certbot test requires real infrastructure — skipping
--- Auto-renew timer status ---
[PASS] Certbot auto-renew timer is active
[PASS] Auto-renew has a next run time
============================================================================
Results: 7 passed, 0 failed, 2 skipped (9 total)
============================================================================
Running: 08-mtls-rotation.sh
============================================================================
Lamaste E2E: 08 — mTLS Rotation
============================================================================
--- Pre-flight: check onboarding is complete ---
--- Current cert fingerprint (before rotation) ---
[INFO] Current cert fingerprint: sha256 Fingerprint=8D:54:F0:97:FC:BF:FB:24:E2:08:79:4D:32:97:91:21:E3:8B:5E:A0:19:60:5F:41:99:B2:9B:D5:1A:F3:8D:D9
--- Rotate mTLS certificate ---
[PASS] Rotation response contains p12 password
[PASS] Rotation response contains expiry: 2028-03-29T13:06:46.000Z
[INFO] Rotation warning: Your current browser certificate is now invalid. Download and import the new certificate before closing this page.
--- Download rotated certificate ---
[PASS] Downloaded client.p12 (HTTP 200)
[PASS] Downloaded file is a valid PKCS12
[INFO] New cert fingerprint: sha256 Fingerprint=07:F9:91:82:D0:73:CA:C0:2E:35:1C:D1:BA:FA:72:3D:5C:F6:0F:30:5C:45:EB:53:0C:59:10:4D:3F:9A:28:0B
[PASS] New cert has different fingerprint than old cert
--- Verify API access with current credentials ---
[PASS] API still accessible after rotation
============================================================================
Results: 6 passed, 0 failed, 0 skipped (6 total)
============================================================================
Running: 09-ip-fallback.sh
============================================================================
Lamaste E2E: 09 — IP Fallback
============================================================================
--- Determine server IP ---
[INFO] Server IP: 10.13.37.1
--- Health endpoint via IP ---
[PASS] Health endpoint accessible via IP:9292
--- Static files via IP ---
[PASS] Panel client served via IP (HTTP 200)
--- Onboarding status via IP ---
[INFO] Onboarding status via IP: COMPLETED
[PASS] Onboarding status endpoint works via IP
--- Management API via IP (if onboarding complete) ---
[PASS] Services endpoint works via IP (4 services)
[PASS] Tunnels endpoint works via IP
[PASS] Users endpoint works via IP
[PASS] System stats endpoint works via IP
--- IP access independence from domain nginx ---
[PASS] IP fallback is reliable (second check)
============================================================================
Results: 8 passed, 0 failed, 0 skipped (8 total)
============================================================================
Running: 10-resilience.sh
============================================================================
Lamaste E2E: 10 — Resilience
============================================================================
--- Pre-flight: check onboarding is complete ---
[INFO] Service nginx status before tests: active
[INFO] Service chisel status before tests: active
[INFO] Service authelia status before tests: active
[INFO] Service lamaste-panel status before tests: active
--- nginx failure and recovery ---
[INFO] Stopping nginx...
[PASS] API shows nginx as 'inactive' after stop
[PASS] nginx restart via API returned ok: true
[PASS] nginx is active after API restart
[PASS] API shows nginx as active after restart
--- chisel failure and recovery ---
[INFO] Stopping chisel...
[PASS] API shows chisel as 'inactive' after stop
[PASS] chisel restart via API returned ok: true
[PASS] chisel is active after API restart
--- authelia failure and recovery ---
[INFO] Stopping authelia...
[PASS] API shows authelia as 'inactive' after stop
[PASS] authelia restart via API returned ok: true
[PASS] authelia is active after API restart
--- Panel survives all service disruptions ---
[PASS] Panel health is ok after all disruptions
[PASS] Service nginx is active at end of resilience test
[PASS] Service chisel is active at end of resilience test
[PASS] Service authelia is active at end of resilience test
[PASS] Service lamaste-panel is active at end of resilience test
============================================================================
Results: 15 passed, 0 failed, 0 skipped (15 total)
============================================================================
Running: 11-input-validation.sh
============================================================================
Lamaste E2E: 11 — Input Validation & Security Hardening
============================================================================
--- Pre-flight: check onboarding is complete ---
--- Invalid UUID for tunnel operations ---
[PASS] PATCH /api/tunnels/not-a-uuid returns 400
[PASS] DELETE /api/tunnels/not-a-uuid returns 400
[PASS] PATCH /api/tunnels/../etc/passwd rejected (HTTP 404)
--- Invalid UUID for site operations ---
[PASS] DELETE /api/sites/not-a-uuid returns 400
--- Invalid invite token format ---
[PASS] GET /api/invite/not-a-valid-token returns 400
[PASS] POST /api/invite/not-a-valid-token/accept returns 400
[PASS] Path traversal does not expose /etc/passwd
--- Invalid domain format in certs endpoint ---
[PASS] POST /api/certs/a..b/renew returns 400
[PASS] POST /api/certs/.../renew returns 400
[PASS] POST /api/certs/evil.com;inject/renew returns 400
--- Subdomain injection attempts ---
[PASS] Subdomain with semicolon rejected (HTTP 400)
[PASS] Subdomain with newline rejected (HTTP 400)
[PASS] Subdomain with path traversal rejected (HTTP 400)
[PASS] Subdomain with uppercase rejected (HTTP 400)
[PASS] Subdomain with 64 chars rejected (HTTP 400)
--- Port boundary validation ---
[PASS] Port 0 rejected (HTTP 400)
[PASS] Port 1023 rejected (HTTP 400)
[PASS] Port 65536 rejected (HTTP 400)
[PASS] Port -1 rejected (HTTP 400)
[PASS] Port 'abc' (string) rejected (HTTP 400)
--- Malformed JSON bodies ---
[PASS] Invalid JSON body to /api/tunnels returns 400
[PASS] Empty body to /api/users rejected (HTTP 400)
--- File permissions ---
[PASS] /etc/lamaste/tunnels.json has correct permissions (600)
[SKIP] /etc/lamaste/sites.json not found
[PASS] panel.json has correct permissions (640)
============================================================================
Results: 24 passed, 0 failed, 1 skipped (25 total)
============================================================================
Running: 12-user-invitations.sh
============================================================================
Lamaste E2E: 12 — User Invitations
============================================================================
--- Pre-flight: check onboarding is complete ---
--- Create invitation ---
[PASS] Invitation creation returned ok: true
[PASS] Invitation username matches
[PASS] Invitation email matches
[PASS] Invitation token is valid 64-char hex
[PASS] Invitation ID is present
[PASS] Invitation createdAt is present
[PASS] Invitation expiresAt is present
--- List invitations ---
[PASS] Invitation appears in GET /api/invitations
[PASS] Token is not exposed in invitation list
[PASS] Invitation status is pending
--- Duplicate invitation ---
[PASS] Duplicate invitation for same username rejected (HTTP 409)
--- Validation: invalid input ---
[PASS] Incomplete invitation data rejected (HTTP 400)
[PASS] Invalid email rejected (HTTP 400)
--- Get invitation details (public endpoint) ---
[PASS] Public invite details show username
[PASS] Public invite details show email
[PASS] Public invite details show expiresAt
--- Invalid token ---
[PASS] Accept with invalid token returns 404
[PASS] Malformed token rejected (HTTP 400)
--- Accept invitation (public endpoint) ---
[PASS] Invitation acceptance returned ok: true
[PASS] Accepted username matches
--- Verify invited user exists ---
[PASS] Invited user appears in GET /api/users
[PASS] Invited user email matches
--- Invitation marked as accepted ---
[PASS] Invitation status changed to accepted
--- Used token rejection ---
[PASS] Reusing accepted token returns 410 Gone
[PASS] GET on used token returns 410 Gone
--- Accept with short password ---
[PASS] Short password rejected on invite accept (HTTP 400)
--- Cleanup: delete invited user ---
[PASS] Invited user deletion returned ok: true
[PASS] Invited user no longer in list after deletion
============================================================================
Results: 28 passed, 0 failed, 0 skipped (28 total)
============================================================================
Running: 13-site-lifecycle.sh
============================================================================
Lamaste E2E: 13 — Site Lifecycle
============================================================================
--- Pre-flight: check onboarding is complete ---
[PASS] Onboarding is complete
--- Create managed static site ---
[PASS] Site creation returned ok: true
[PASS] Site has an ID
[PASS] Site name matches
[PASS] Site type is managed
[INFO] Created site: e2esite.test.lamaste.local (ID: ae423cb6-1bde-4745-915c-68f469c426f6)
--- Verify site in listing ---
[PASS] Site appears in listing
--- List files — default content ---
[PASS] Site has default files (count: 1)
[PASS] Default index.html exists
--- Upload test file ---
[PASS] File upload returned ok: true
--- Verify uploaded file in listing ---
[PASS] Uploaded file appears in listing
--- Delete uploaded file ---
[PASS] File deletion returned ok: true
--- Verify file removed ---
[PASS] Deleted file no longer in listing
--- Update site settings ---
[PASS] Settings update returned ok: true
[PASS] SPA mode is now enabled
[PASS] SPA mode persisted in listing
--- File extension validation ---
[PASS] Upload of .php file rejected with 400
[PASS] Upload of .exe file rejected with 400
[PASS] Upload of file with no extension rejected with 400
[PASS] Upload of .css file succeeds
--- Input validation ---
[PASS] Duplicate site name rejected with 400
[PASS] Reserved name 'panel' rejected with 400
[PASS] Reserved name 'auth' rejected with 400
[PASS] Invalid UUID rejected with 400
--- Delete site ---
[PASS] Site deletion returned ok: true
--- Verify site removed ---
[PASS] Deleted site no longer in listing
[PASS] Deleted site returns 404
============================================================================
Results: 26 passed, 0 failed, 0 skipped (26 total)
============================================================================
Running: 15-plugin-lifecycle.sh
============================================================================
Lamaste E2E: 15 — Plugin Lifecycle
============================================================================
--- Pre-flight: check onboarding is complete ---
[PASS] Onboarding is complete
--- Empty initial plugin list ---
[PASS] Initial plugin list is empty
--- Plugin install validation ---
[PASS] Non-@lamalibre package rejected (HTTP 400)
[PASS] Empty package name rejected (HTTP 400)
--- Plugin detail for non-existent plugin ---
[PASS] GET non-existent plugin returns 404
--- Enable/disable non-existent plugin ---
[PASS] Enable non-existent plugin returns 404
[PASS] Disable non-existent plugin returns 404
--- Uninstall non-existent plugin ---
[PASS] Uninstall non-existent plugin returns 404
--- Push install config defaults ---
[PASS] Push install is disabled by default
[PASS] Default policy ID is 'default'
[PASS] At least one push install policy exists (count: 1)
--- Push install config update ---
[PASS] PATCH push-install config returned ok: true
[PASS] Push install is now enabled
--- Create a push install policy ---
[PASS] Policy creation returned ok: true
[PASS] Policy ID matches
--- Verify policy in listing ---
[PASS] Created policy appears in listing
--- Update the push install policy ---
[PASS] Policy update returned ok: true
[PASS] Description updated
--- Cannot delete the default push install policy ---
[PASS] Cannot delete the default policy (HTTP 400)
--- Delete the e2e-pi-test policy ---
[PASS] Policy deletion returned ok: true
[PASS] Deleted policy no longer in listing
--- Push install policy validation ---
[PASS] POST policy with empty name rejected (HTTP 400)
[PASS] POST policy with duplicate ID rejected (HTTP 409)
[PASS] PATCH non-existent policy returns 404
[PASS] DELETE non-existent policy returns 404
--- Push install enable/disable for agent ---
[INFO] Found agent: test-agent
[PASS] Push install enable for agent returned ok: true
[PASS] pushInstallEnabledUntil is set
[PASS] Push install disable for agent returned ok: true
--- Push install without global toggle ---
[PASS] Cannot enable push install when globally disabled (HTTP 400)
--- Push install sessions audit log ---
[PASS] GET push-install sessions returns a sessions array
--- Push install input validation ---
[PASS] POST enable with durationMinutes: 0 rejected (HTTP 400)
[PASS] POST enable with durationMinutes: 9999 rejected (HTTP 400)
[PASS] PATCH config with non-existent defaultPolicy rejected (HTTP 400)
[PASS] POST enable for non-existent agent returns 404
[PASS] DELETE enable for non-existent agent returns 404
[PASS] POST enable with invalid label format rejected (HTTP 400)
[PASS] GET plugin with invalid name rejected (HTTP 400)
--- Cleanup ---
[PASS] Push install disabled globally for cleanup
[PASS] Push install is disabled after cleanup
[PASS] Cleanup complete — plugin state restored
============================================================================
Results: 40 passed, 0 failed, 0 skipped (40 total)
============================================================================
Running: 16-enrollment-tokens.sh
============================================================================
Lamaste E2E: 16 — Hardware-Bound Certificate Enrollment
============================================================================
--- Pre-flight: check onboarding is complete ---
--- Admin auth mode defaults to p12 ---
[PASS] Admin auth mode is p12 by default
--- Create enrollment token ---
[PASS] Token creation returns ok: true
[PASS] Token is not empty
[PASS] Token has expiresAt
[PASS] Token response contains correct label
--- Duplicate token for same label rejected ---
[PASS] Duplicate token for active label returns 409
--- Public enrollment endpoint reachable without mTLS ---
[PASS] Enrollment endpoint reachable without mTLS (HTTP 400)
--- Enrollment with invalid token rejected ---
[PASS] Invalid token rejected with correct message
--- Enroll agent with valid token + CSR ---
[PASS] Enrollment returns ok: true
[PASS] Enrolled label matches
[PASS] Enrollment returns signed certificate
[PASS] Enrollment returns CA certificate
[PASS] Enrollment returns serial number
[PASS] Signed cert has correct CN
--- Token replay rejected (single-use) ---
[PASS] Token replay returns 401
--- Enrolled agent visible in agent list with hardware-bound method ---
[PASS] Agent shows enrollmentMethod: hardware-bound
--- P12 download hidden for hardware-bound agent ---
[PASS] P12 download returns 404 for hardware-bound agent (no P12 on disk)
--- Clean up: revoke test agent ---
[PASS] Revoked enrollment test agent
--- Admin upgrade to hardware-bound ---
[PASS] Admin upgrade returns ok: true
[PASS] Admin upgrade returns signed certificate
--- P12 lockdown after admin upgrade ---
[PASS] P12 rotation blocked after admin upgrade (HTTP 000000)
--- Revert admin to P12 mode (for other tests) ---
[PASS] Reverted admin to P12 mode with fresh cert
[PASS] Admin auth mode reverted to p12
============================================================================
Results: 23 passed, 0 failed, 0 skipped (23 total)
============================================================================
Running: 17-panel-2fa.sh
============================================================================
Lamaste E2E: 17 — Panel Built-in TOTP 2FA
============================================================================
--- Pre-flight: check onboarding is complete ---
--- Default state: 2FA disabled ---
[PASS] 2FA is disabled by default
[PASS] setupComplete is false by default
--- Setup: generate TOTP secret ---
[PASS] Setup returns otpauth URI
[PASS] Setup returns manual key
[PASS] URI is valid otpauth format
--- Confirm 2FA with valid code ---
[PASS] Generated TOTP code
[INFO] Generated TOTP code: 160308
[PASS] 2FA is now enabled
[PASS] Session cookie received on confirm
[PASS] Status shows enabled after confirm
--- IP vhost disabled after enabling 2FA ---
[PASS] IP:9292 vhost is disabled (HTTP 000)
--- Request without session returns 401 2fa_required ---
[PASS] Request without session cookie returns 401
--- Authenticated request with session cookie ---
[PASS] Authenticated request with session cookie returns system stats
--- Disable 2FA ---
[INFO] Waiting 18s for next TOTP window...
[PASS] 2FA disabled successfully
--- IP vhost re-enabled after disabling 2FA ---
[PASS] IP:9292 vhost is re-enabled after disabling 2FA
[PASS] 2FA status is disabled
--- Reset admin clears 2FA ---
[PASS] 2FA re-enabled for reset test
Download the P12 from the panel or copy it
manually from the server.
============================================
[PASS] 2FA disabled after reset-admin
[PASS] IP vhost restored after reset-admin
--- Rate limiting on wrong codes ---
[PASS] Rate limiting kicks in after 6 wrong attempts (HTTP 429)
============================================================================
Results: 19 passed, 0 failed, 0 skipped (19 total)
============================================================================
Running: 18-json-installer.sh
============================================================================
Lamaste E2E: 18 — JSON Installer Output
============================================================================
--- create-lamaste --json (redeploy mode) ---
--- NDJSON line validation ---
[PASS] All 5 lines are valid JSON
[PASS] Step events emitted: 4
--- Complete event validation ---
[PASS] Exactly one complete event emitted
[PASS] Server IP present: 192.168.2.9
[PASS] Panel URL present and uses HTTPS: https://192.168.2.9:9292
[PASS] P12 path within expected directory: /etc/lamaste/pki/client.p12
[PASS] P12 password path within expected directory: /etc/lamaste/pki/.p12-password
--- Step status validation ---
[PASS] check_environment step present
[PASS] All step events have valid status values
--- Panel health after redeploy ---
[PASS] Panel healthy after --json redeploy
============================================================================
Results: 10 passed, 0 failed, 0 skipped (10 total)
============================================================================
Running: 19-panel-expose.sh
============================================================================
Lamaste E2E: 19 — Panel Expose Lifecycle
============================================================================
--- Pre-flight: check onboarding is complete ---
--- Verify panel:expose is a valid capability ---
[PASS] Agent cert with panel:expose created successfully
[PASS] Agent cert has a p12 password
[INFO] Created agent cert: panel-e2e-1774876078
[PASS] Extracted PEM cert and key from .p12
--- Expose panel: check agent-panel-status before expose ---
[PASS] Panel not exposed initially
[PASS] No FQDN before expose
--- Expose panel: POST /api/tunnels/expose-panel ---
[PASS] Expose panel returned ok: true
[PASS] Panel tunnel has an ID
[PASS] Panel tunnel type is 'panel'
[PASS] Panel subdomain matches agent-<label>
[PASS] Panel tunnel port matches
[PASS] Panel tunnel has an FQDN
[PASS] Panel tunnel has a createdAt timestamp
[PASS] Panel tunnel agentLabel matches
[INFO] Exposed panel tunnel: agent-panel-e2e-1774876078.test.lamaste.local (ID: d78eba91-ecdf-4f01-8941-754b2332c6c0)
--- Verify panel tunnel in tunnel listing ---
[PASS] Panel tunnel shows type 'panel' in listing
[PASS] Panel tunnel shows correct agentLabel in listing
--- Verify nginx mTLS vhost created (not app vhost) ---
[PASS] mTLS panel vhost exists at /etc/nginx/sites-enabled/lamaste-agent-panel-agent-panel-e2e-1774876078
[PASS] No app vhost created (correct — panel uses mTLS vhost)
[PASS] nginx -t passes after panel expose
--- Verify agent-panel-status after expose ---
[PASS] Panel shows as enabled after expose
[PASS] Panel status FQDN matches
[PASS] Panel status port matches
--- Duplicate expose returns 409 ---
[PASS] Duplicate panel expose returns 409 Conflict
--- Validation: agent- prefix reserved for non-panel tunnels ---
[PASS] agent- prefix rejected for non-panel tunnel (HTTP 400)
--- Capability check: agent without panel:expose gets 403 ---
[PASS] Agent cert without panel:expose created
[PASS] Expose panel returns 403 without panel:expose capability
[PASS] Agent panel status returns 403 without panel:expose capability
[PASS] Retract panel returns 403 without panel:expose capability
--- Capability check: PATCH panel tunnel requires panel:expose ---
[PASS] PATCH panel tunnel returns 403 without panel:expose
--- Capability check: DELETE panel tunnel requires panel:expose ---
[PASS] DELETE panel tunnel returns 403 without panel:expose
--- Cross-agent spoofing: generic POST /api/tunnels with type=panel ---
[PASS] Cross-agent panel tunnel spoofing rejected (HTTP 403)
--- Retract panel: DELETE /api/tunnels/retract-panel ---
[PASS] Retract panel returned ok: true
[PASS] Panel tunnel no longer in list after retract
[PASS] mTLS panel vhost removed after retract
[PASS] nginx -t passes after panel retract
--- Verify agent-panel-status after retract ---
[PASS] Panel shows as disabled after retract
--- Retract nonexistent panel returns 404 ---
[PASS] Retract nonexistent panel returns 404
--- Validation: expose-panel with invalid port ---
[PASS] Port below 1024 rejected (HTTP 400)
============================================================================
Results: 37 passed, 0 failed, 0 skipped (37 total)
============================================================================
[INFO] Cleaning up test resources...
{"error":"No panel tunnel found for this agent"}{"ok":true,"label":"panel-e2e-1774876078"}{"ok":true,"label":"nopanel-e2e"} Running: 20-agent-json-setup.sh
============================================================================
Lamaste E2E: 20 — Agent JSON Setup Output
============================================================================
--- Pre-flight: check onboarding is complete ---
[SKIP] lamaste-agent not found in PATH
============================================================================
Results: 0 passed, 0 failed, 1 skipped (1 total)
============================================================================
Running: 21-identity-system.sh
============================================================================
Lamaste E2E: 21 — Identity System
============================================================================
--- Pre-flight: check onboarding is complete ---
--- GET /api/identity/users (admin) ---
[PASS] GET /api/identity/users returns { users: [...] } array (count: 2)
[PASS] User object has 'username' field
[PASS] User object has 'displayname' field
[PASS] User object has 'email' field
[PASS] User object has 'groups' field
[PASS] No 'password' field in identity users response
[PASS] No bcrypt hash in identity users response
--- GET /api/identity/users/:username (admin) ---
[PASS] Single user lookup returns correct username
[PASS] Single user lookup returns 200
[PASS] Nonexistent user returns 404
--- GET /api/identity/groups (admin) ---
[PASS] GET /api/identity/groups returns { groups: [...] } array (count: 1)
[INFO] Only 1 group(s) — sort order trivially correct
[PASS] Groups endpoint matches groups extracted from user list
--- GET /api/identity/self (admin, mTLS vhost) ---
[PASS] identity/self returns 400 with appropriate message on mTLS vhost
[PASS] identity/self returns HTTP 400 on mTLS vhost
--- Input validation — invalid username parameter ---
[PASS] Username with special characters returns 400
[PASS] Username with path traversal returns 400
[PASS] Nonexistent identity sub-path returns 404
--- identity:query capability gating ---
[PASS] Agent cert without identity:query created
[PASS] Extracted agent PEM cert and key from .p12
[PASS] Agent without identity:query gets 403 on /api/identity/users
[PASS] Agent without identity:query gets 403 on /api/identity/groups
[PASS] Agent capabilities updated to include identity:query
[PASS] Agent with identity:query gets 200 on /api/identity/users
[PASS] Agent with identity:query gets 200 on /api/identity/groups
--- Reserved API prefix: 'identity' in RESERVED_API_PREFIXES ---
[PASS] 'identity' prefix is reserved (ticket scope registration rejected with HTTP 400)
============================================================================
Results: 25 passed, 0 failed, 0 skipped (25 total)
============================================================================
[INFO] Cleaning up identity test resources...
{"ok":true,"label":"identity-e2e-1774876089"}
============================================================================
Test Suite Summary
============================================================================
[PASS] 01-fresh-install.sh
[PASS] 02-mtls-enforcement.sh
[PASS] 03-onboarding-flow.sh
[PASS] 04-tunnel-lifecycle.sh
[PASS] 05-user-lifecycle.sh
[PASS] 06-service-control.sh
[PASS] 07-cert-renewal.sh
[PASS] 08-mtls-rotation.sh
[PASS] 09-ip-fallback.sh
[PASS] 10-resilience.sh
[PASS] 11-input-validation.sh
[PASS] 12-user-invitations.sh
[PASS] 13-site-lifecycle.sh
[PASS] 15-plugin-lifecycle.sh
[PASS] 16-enrollment-tokens.sh
[PASS] 17-panel-2fa.sh
[PASS] 18-json-installer.sh
[PASS] 19-panel-expose.sh
[PASS] 20-agent-json-setup.sh
[PASS] 21-identity-system.sh
Total: 20 tests — 20 passed, 0 failed
SUITE PASSED