import { test, expect } from '@playwright/test'; import { login, BASE_URL } from '../../helpers/auth'; import { markManual, markPartial, markWrite } from '../../helpers/manual'; import { attachWriteResult } from '../../helpers/writeResult'; test.describe('core-security', () => { test.beforeEach(async ({ page }) => { await login(page); }); test('core-security-write-strong_password_policy', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'automated' }); markWrite(); testInfo.annotations.push({ type: 'description', description: 'Navigate to security settings and record the current password policy configuration' }); const captured: Record = {}; await test.step('Navigate to Security → Password Policy', async () => { await page.goto(`${BASE_URL}/admin/security/password-policy`); }); await test.step('Extract policy fields', async () => { const fields = ['minimum length', 'complexity', 'expiry', 'history', 'lockout']; for (const field of fields) { const el = page.locator(`[name*="password"], input, select`).filter({ hasText: new RegExp(field, 'i') }); if (await el.count() > 0) { captured[field] = await el.first().inputValue().catch(() => 'n/a'); } } captured['pageText'] = await page.locator('main, form, .settings-panel').first().textContent(); }); await test.step('Attach password policy record', async () => { await attachWriteResult(testInfo, captured, 'password-policy'); }); expect(captured['pageText']).toBeTruthy(); }); test('core-security-check-sip_tls_and_srtp_encryption_enabled_by_default', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'automated' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); testInfo.annotations.push({ type: 'description', description: 'Verify SIP TLS and SRTP media encryption are enabled by default in a fresh installation' }); await test.step('Navigate to SIP / Security settings', async () => { await page.goto(`${BASE_URL}/admin/security/sip`); }); await test.step('Verify SIP TLS is enabled', async () => { const tlsToggle = page.locator('[name*="tls"],[id*="tls"],[data-field*="tls"]').first(); await expect(tlsToggle).toBeChecked(); }); await test.step('Verify SRTP is enabled', async () => { const srtpToggle = page.locator('[name*="srtp"],[id*="srtp"],[data-field*="srtp"]').first(); await expect(srtpToggle).toBeChecked(); }); }); test('core-security-check-ip_access_control_lists', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'automated' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); testInfo.annotations.push({ type: 'description', description: 'Verify IP Access Control Lists section is accessible and correctly configured' }); await test.step('Navigate to Security → IP ACL / Firewall', async () => { await page.goto(`${BASE_URL}/admin/security/acl`); }); await test.step('Verify ACL section loads without errors', async () => { await expect(page.locator('body')).not.toContainText(/500|unexpected error/i); }); await test.step('Verify ACL rules list is displayed', async () => { await expect(page.locator('table, .acl-list, .rule-list, .ip-list').first()).toBeVisible(); }); await test.step('Verify at least one allow/deny rule exists', async () => { const rules = page.locator('tr, .acl-entry').filter({ hasText: /allow|deny|permit|block/i }); await expect(rules.first()).toBeVisible(); }); }); test('core-security-check-unnecessary_services_and_ports_are_disabled', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'partial' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); markPartial('Service status can be checked via admin portal; comprehensive port scanning requires nmap from an external host — integrate nmap into CI pipeline separately'); testInfo.annotations.push({ type: 'description', description: 'Verify unnecessary services are disabled and only required ports are open' }); await test.step('Navigate to Services / System Status panel', async () => { await page.goto(`${BASE_URL}/admin/system/services`); }); await test.step('Verify listed services show only expected running state', async () => { await expect(page.locator('body')).not.toContainText(/ftp|telnet|rsh|rlogin/i); }); await test.step('Verify remote management uses SSH (not Telnet)', async () => { await expect(page.locator('body')).not.toContainText(/telnet.*enabled|telnet.*active/i); }); // TODO_MANUAL: From an external host, run: // nmap -p- --open // and verify that only the expected ports (e.g., 443, 5060, 5061, 10000-20000/udp) are open. }); test('core-security-check-security_event_logging_and_audit_trail_is_active', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'automated' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); testInfo.annotations.push({ type: 'description', description: 'Verify security event logging and audit trail are active and recording events' }); await test.step('Navigate to Security → Audit Log', async () => { await page.goto(`${BASE_URL}/admin/security/audit`); }); await test.step('Verify audit log page loads', async () => { await expect(page.locator('body')).not.toContainText(/500|not found/i); }); await test.step('Verify at least one audit entry is visible', async () => { await expect(page.locator('table, .log-list, .audit-list, .event-list').first()).toBeVisible(); }); await test.step('Trigger a login event and verify it appears in the log', async () => { await page.goto(`${BASE_URL}/admin/security/audit?filter=login`); const logEntry = page.locator('td, .log-entry').filter({ hasText: /login|authentication|auth/i }); await expect(logEntry.first()).toBeVisible(); }); }); test('core-security-check-auto_enrollment_certificate_management_process', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'automated' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); testInfo.annotations.push({ type: 'description', description: 'Verify auto-enrollment certificate management (ACME/Let\'s Encrypt or internal CA) is configured and functional' }); await test.step('Navigate to Security → Certificates', async () => { await page.goto(`${BASE_URL}/admin/security/certificates`); }); await test.step('Verify certificate management page loads', async () => { await expect(page.locator('body')).not.toContainText(/500|not found/i); }); await test.step('Verify active certificate is displayed', async () => { const certInfo = page.locator('.cert-info, .certificate-item, table').first(); await expect(certInfo).toBeVisible(); }); await test.step('Verify certificate has a valid expiry date in the future', async () => { const pageText = await page.locator('body').textContent() ?? ''; expect(pageText).toMatch(/valid|expir|expires|auto|renew/i); }); }); test('core-security-check-known_vulnerable_dependencies_in_release_artifacts', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'partial' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); markPartial('CVE scanning is automated with Trivy/OWASP-DC integrated in CI; Playwright verifies the scan report page in the portal. Actual scanning runs outside this browser test.'); testInfo.annotations.push({ type: 'description', description: 'Verify release artifacts do not contain known vulnerable dependencies (CVE scan via Trivy / OWASP Dependency-Check)' }); await test.step('Navigate to Security → Vulnerability Report (if available in portal)', async () => { await page.goto(`${BASE_URL}/admin/security/vulnerabilities`); }); await test.step('Verify vulnerability report section loads', async () => { await expect(page.locator('body')).not.toContainText(/500/i); }); await test.step('Verify no Critical or High severity CVEs are listed as unresolved', async () => { const criticals = page.locator('.severity-critical, [data-severity="critical"], td').filter({ hasText: /critical.*unresolved|unresolved.*critical/i }); await expect(criticals).toHaveCount(0); }); // TODO_MANUAL: Run `trivy image ` or OWASP Dependency-Check on release artifacts // and assert exit code 0 (no Critical/High findings). }); test('core-security-check-previous_security_findings_have_been_remediated_before_release', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'manual' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); markManual('Requires access to security issue tracker (Jira/GitHub Security Advisories) and cross-referencing resolved CVEs with the release changelog; human judgment required to confirm completeness'); testInfo.annotations.push({ type: 'description', description: 'Verify all previously identified security findings are remediated before this release ships' }); // Manual steps: // 1. Pull the list of open security findings from the issue tracker (label: security, milestone: this release). // 2. For each finding, verify the fix commit is included in this release tag. // 3. Re-test each finding to confirm it is no longer reproducible. // 4. Obtain written confirmation from the security lead. // 5. Update finding status to "Resolved" in the tracker. }); test('core-security-check-obtain_formal_security_team_sign_off_for_the_new_version', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'manual' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); markManual('This is a governance gate requiring a human decision and formal sign-off document; cannot be automated'); testInfo.annotations.push({ type: 'description', description: 'Obtain formal written sign-off from the Security team approving the release version' }); // Manual steps: // 1. Compile the Security Test Summary report. // 2. Submit to the Security team for review at least 3 business days before release. // 3. Security team reviews findings, risk acceptance, and remediation completeness. // 4. Security lead signs the release approval document. // 5. Store signed document in the release artefacts folder. }); test('core-security-check-verify_role_based_access_control_and_least_privilege_principle', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'automated' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); testInfo.annotations.push({ type: 'description', description: 'Verify RBAC roles are correctly defined and least-privilege principle is enforced in the admin portal' }); await test.step('Navigate to Users → Roles & Permissions', async () => { await page.goto(`${BASE_URL}/admin/users/roles`); }); await test.step('Verify roles list is visible', async () => { await expect(page.locator('table, .role-list, .roles-panel').first()).toBeVisible(); }); await test.step('Verify at least Admin and Read-Only roles exist', async () => { const bodyText = (await page.locator('body').textContent())?.toLowerCase() ?? ''; expect(bodyText).toMatch(/admin|administrator/i); expect(bodyText).toMatch(/read.?only|viewer|readonly/i); }); await test.step('Verify a read-only user cannot access admin settings', async () => { // This step validates the UI hides restricted controls for non-admin roles await page.goto(`${BASE_URL}/admin/users/roles`); const adminOnlyEl = page.locator('[data-role="admin"], [data-permission="admin-only"]'); await expect(adminOnlyEl.first()).toBeVisible(); }); }); test('core-security-check-encryption_of_sensitive_data_at_rest_and_in_transit', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'automationStatus', description: 'partial' }); testInfo.annotations.push({ type: 'testType', description: 'check' }); markPartial('TLS/HTTPS enforcement and encryption settings can be verified via admin portal; database-level at-rest encryption requires file-system access or CLI commands outside Playwright scope'); testInfo.annotations.push({ type: 'description', description: 'Verify sensitive data is encrypted at rest (DB encryption) and in transit (HTTPS/TLS)' }); await test.step('Verify admin portal is served over HTTPS', async () => { expect(BASE_URL).toMatch(/^https:\/\//); await page.goto(BASE_URL); expect(page.url()).toMatch(/^https:\/\//); }); await test.step('Navigate to Security → Encryption settings', async () => { await page.goto(`${BASE_URL}/admin/security/encryption`); }); await test.step('Verify encryption settings page is accessible', async () => { await expect(page.locator('body')).not.toContainText(/500|not found/i); }); // TODO_MANUAL: SSH into the server and verify: // - DB files are stored on an encrypted volume or DB-level encryption is enabled. // - `openssl s_client -connect :443` shows TLS 1.2+ and strong cipher suite. }); });