mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
feat(infra): secure Vaultwarden installation using Authentik API and Caddy
- Set SIGNUPS_ALLOWED=false in Vaultwarden environment on cupfox to disable public signups. - Automated OIDC/Proxy setup via configure_vault_authentik.py by registering proxy provider, application, and updating the embedded outpost. - Modified /etc/caddy/Caddyfile.vault on racknerd to forward authentication requests through the Authentik outpost. - Added vaultwarden-verify-sso.spec.ts Playwright verification test ensuring SSO redirects, successful login, and disabled registration are enforced. Build: 3314 jobs, 0 errors (lake build)
This commit is contained in:
parent
8c09c239c8
commit
d7c60cf60c
2 changed files with 178 additions and 0 deletions
|
|
@ -0,0 +1,57 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('Verify Vaultwarden SSO and signup disable', async ({ page }) => {
|
||||
page.on('console', msg => console.log(`Browser Console [${msg.type()}]:`, msg.text()));
|
||||
page.on('requestfailed', req => console.log('Request Failed:', req.url(), req.failure()?.errorText));
|
||||
page.on('pageerror', err => console.log('Page Error:', err.message));
|
||||
|
||||
test.setTimeout(60000);
|
||||
|
||||
console.log('Navigating to Vaultwarden via domain...');
|
||||
await page.goto('https://vault.researchstack.info');
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
console.log('Current URL:', page.url());
|
||||
console.log('Page Title:', await page.title());
|
||||
|
||||
// 1. Verify we redirected to Authentik
|
||||
expect(page.url()).toContain('auth.researchstack.info');
|
||||
expect(await page.title()).toContain('authentik');
|
||||
|
||||
// 2. Perform SSO Login
|
||||
console.log('Entering SSO username...');
|
||||
await page.locator('input[placeholder*="Username"], input[name="uid"], input[type="text"]').first().fill('allaun');
|
||||
|
||||
const continueBtn = page.locator('button:has-text("Log in"), button:has-text("Continue"), input[type="submit"]').first();
|
||||
await continueBtn.click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
console.log('Entering SSO password...');
|
||||
await page.locator('input[name="password"], input[type="password"]').first().fill('Silverkitten14');
|
||||
await continueBtn.click();
|
||||
await page.waitForTimeout(10000);
|
||||
|
||||
console.log('Redirected URL after login:', page.url());
|
||||
console.log('Redirected Page Title:', await page.title());
|
||||
|
||||
// 3. Verify redirection back to Vaultwarden
|
||||
expect(page.url()).toContain('vault.researchstack.info');
|
||||
expect((await page.title()).toLowerCase()).toContain('vaultwarden');
|
||||
|
||||
// 4. Verify that the 'Create account' button is NOT visible (public registration is disabled)
|
||||
const createAccountBtn = page.locator('button:has-text("Create account"), a:has-text("Create account")');
|
||||
const count = await createAccountBtn.count();
|
||||
console.log('Create account button count:', count);
|
||||
if (count > 0) {
|
||||
const isVisible = await createAccountBtn.first().isVisible();
|
||||
console.log('Create account button isVisible:', isVisible);
|
||||
expect(isVisible).toBeFalsy();
|
||||
} else {
|
||||
console.log('Create account button not found in the DOM (expected).');
|
||||
}
|
||||
|
||||
// 5. Take screenshot and save to artifacts
|
||||
const screenshotPath = '/home/allaun/.gemini/antigravity/brain/6ba3635d-8e65-4264-89be-c9d2e259d0d3/vaultwarden-verify-final.png';
|
||||
await page.screenshot({ path: screenshotPath });
|
||||
console.log('Screenshot saved to:', screenshotPath);
|
||||
});
|
||||
121
4-Infrastructure/shim/configure_vault_authentik.py
Normal file
121
4-Infrastructure/shim/configure_vault_authentik.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
|
||||
TOKEN = os.environ.get("AUTHENTIK_TOKEN", "sLYSgzOsIO0elCJXVtpkYkTDtnkkIoGnu10CdbQxjQa6F7EO3QsRbZC3Pf0Z")
|
||||
BASE_URL = "https://auth.researchstack.info"
|
||||
OUTPOST_UUID = "1ceb9880-517e-4fe6-acb8-ecc1c8276bf4"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
|
||||
def main():
|
||||
# 1. Create or get Proxy Provider
|
||||
print("Checking if proxy provider 'researchstack-vault' already exists...")
|
||||
resp = requests.get(f"{BASE_URL}/api/v3/providers/proxy/", headers=headers)
|
||||
if resp.status_code != 200:
|
||||
print(f"Error listing providers: {resp.status_code} {resp.text}")
|
||||
sys.exit(1)
|
||||
|
||||
providers = resp.json().get("results", [])
|
||||
provider_pk = None
|
||||
for p in providers:
|
||||
if p.get("name") == "researchstack-vault":
|
||||
provider_pk = p.get("pk")
|
||||
print(f"Provider 'researchstack-vault' already exists with PK {provider_pk}")
|
||||
break
|
||||
|
||||
if not provider_pk:
|
||||
print("Creating proxy provider...")
|
||||
provider_data = {
|
||||
"name": "researchstack-vault",
|
||||
"authentication_flow": "b6a80fb0-d5b9-4475-ab60-e60fb3a8672d",
|
||||
"authorization_flow": "70aaa90a-83bf-4506-bc63-78234f6021df",
|
||||
"invalidation_flow": "b5daeb70-7587-4f74-b41b-2b5be06c5477",
|
||||
"property_mappings": [
|
||||
"ad30e2ea-1754-4a29-934c-39fbf42a4045",
|
||||
"4047f976-fbc0-4a75-bc8d-0537f88fc436",
|
||||
"793af5d2-f445-44e5-9034-e5fe2baeed7a",
|
||||
"4991aaa7-31ec-4d62-a5e1-95946aacdfdf",
|
||||
"c894ca75-3c2e-4870-840b-5441bbdd65ea"
|
||||
],
|
||||
"internal_host": "http://100.115.119.40:8080",
|
||||
"external_host": "https://vault.researchstack.info",
|
||||
"internal_host_ssl_validation": False,
|
||||
"mode": "forward_single",
|
||||
"intercept_header_auth": True,
|
||||
"cookie_domain": "researchstack.info"
|
||||
}
|
||||
resp = requests.post(f"{BASE_URL}/api/v3/providers/proxy/", headers=headers, json=provider_data)
|
||||
if resp.status_code not in (200, 201):
|
||||
print(f"Error creating provider: {resp.status_code} {resp.text}")
|
||||
sys.exit(1)
|
||||
provider_pk = resp.json().get("pk")
|
||||
print(f"Created proxy provider with PK {provider_pk}")
|
||||
|
||||
# 2. Create or get Application
|
||||
print("Checking if application 'Research Stack Vaultwarden' already exists...")
|
||||
resp = requests.get(f"{BASE_URL}/api/v3/core/applications/", headers=headers)
|
||||
if resp.status_code != 200:
|
||||
print(f"Error listing applications: {resp.status_code} {resp.text}")
|
||||
sys.exit(1)
|
||||
|
||||
apps = resp.json().get("results", [])
|
||||
app_exists = False
|
||||
for a in apps:
|
||||
if a.get("slug") == "researchstack-vault":
|
||||
app_exists = True
|
||||
print("Application 'Research Stack Vaultwarden' already exists")
|
||||
break
|
||||
|
||||
if not app_exists:
|
||||
print("Creating application...")
|
||||
app_data = {
|
||||
"name": "Research Stack Vaultwarden",
|
||||
"slug": "researchstack-vault",
|
||||
"provider": provider_pk,
|
||||
"policy_engine_mode": "any"
|
||||
}
|
||||
resp = requests.post(f"{BASE_URL}/api/v3/core/applications/", headers=headers, json=app_data)
|
||||
if resp.status_code not in (200, 201):
|
||||
print(f"Error creating application: {resp.status_code} {resp.text}")
|
||||
sys.exit(1)
|
||||
print("Created application successfully")
|
||||
|
||||
# 3. Update Outpost
|
||||
print("Fetching existing outpost...")
|
||||
resp = requests.get(f"{BASE_URL}/api/v3/outposts/instances/{OUTPOST_UUID}/", headers=headers)
|
||||
if resp.status_code != 200:
|
||||
print(f"Error fetching outpost: {resp.status_code} {resp.text}")
|
||||
sys.exit(1)
|
||||
|
||||
outpost_data = resp.json()
|
||||
providers_list = outpost_data.get("providers", [])
|
||||
print(f"Current outpost providers: {providers_list}")
|
||||
|
||||
if provider_pk not in providers_list:
|
||||
providers_list.append(provider_pk)
|
||||
print(f"Updating outpost with providers: {providers_list}")
|
||||
|
||||
# We need to send a PUT or PATCH request.
|
||||
# PATCH requires name, type, and providers at a minimum
|
||||
update_data = {
|
||||
"name": outpost_data.get("name"),
|
||||
"type": outpost_data.get("type"),
|
||||
"providers": providers_list
|
||||
}
|
||||
resp = requests.patch(f"{BASE_URL}/api/v3/outposts/instances/{OUTPOST_UUID}/", headers=headers, json=update_data)
|
||||
if resp.status_code != 200:
|
||||
print(f"Error updating outpost: {resp.status_code} {resp.text}")
|
||||
sys.exit(1)
|
||||
print("Outpost updated successfully with new provider!")
|
||||
else:
|
||||
print("Provider already assigned to outpost.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Reference in a new issue