diff --git a/frontend/src/components/AccessListForm.tsx b/frontend/src/components/AccessListForm.tsx index 63e3f3a3..86155b15 100644 --- a/frontend/src/components/AccessListForm.tsx +++ b/frontend/src/components/AccessListForm.tsx @@ -408,6 +408,9 @@ export function AccessListForm({ initialData, onSubmit, onCancel, onDelete, isLo {!formData.local_network_only && ( <> +
+ Note: IP-based blocklists (botnets, cloud scanners, VPN ranges) are better handled by CrowdSec, WAF, or rate limiting. Use IP-based ACLs sparingly for static or known ranges. +
diff --git a/frontend/src/data/__tests__/securityPresets.test.ts b/frontend/src/data/__tests__/securityPresets.test.ts index 13e56b07..a2d25da0 100644 --- a/frontend/src/data/__tests__/securityPresets.test.ts +++ b/frontend/src/data/__tests__/securityPresets.test.ts @@ -48,16 +48,10 @@ describe('securityPresets', () => { }); }); - it('blacklist presets have ipRanges', () => { + it('no IP-based blacklist presets are included (CrowdSec handles dynamic IP threats)', () => { const ipPresets = SECURITY_PRESETS.filter((p) => p.type === 'blacklist'); - ipPresets.forEach((preset) => { - expect(preset.ipRanges).toBeDefined(); - expect(preset.ipRanges!.length).toBeGreaterThan(0); - preset.ipRanges!.forEach((rule) => { - expect(rule).toHaveProperty('cidr'); - expect(rule).toHaveProperty('description'); - }); - }); + // IP-based blacklists are deprecated and should be handled by CrowdSec / WAF / rate limiting + expect(ipPresets.length).toBe(0); }); }); @@ -84,9 +78,9 @@ describe('securityPresets', () => { }); }); - it('returns advanced category presets', () => { + it('returns advanced category presets (may be empty)', () => { const advancedPresets = getPresetsByCategory('advanced'); - expect(advancedPresets.length).toBeGreaterThan(0); + expect(Array.isArray(advancedPresets)).toBe(true); advancedPresets.forEach((preset) => { expect(preset.category).toBe('advanced'); }); diff --git a/frontend/src/data/securityPresets.ts b/frontend/src/data/securityPresets.ts index 48e4b60e..a78a2d97 100644 --- a/frontend/src/data/securityPresets.ts +++ b/frontend/src/data/securityPresets.ts @@ -77,127 +77,6 @@ export const SECURITY_PRESETS: SecurityPreset[] = [ dataSourceUrl: 'https://isc.sans.edu/', warning: 'Aggressive blocking. May impact legitimate international users.', }, - { - id: 'known-botnets', - name: 'Block Known Botnet IPs', - description: 'Block IPs known to be part of active botnets and malware networks', - category: 'security', - type: 'blacklist', - ipRanges: [ - // Spamhaus DROP list entries (curated subset) - { cidr: '5.8.10.0/24', description: 'Spamhaus DROP - malware' }, - { cidr: '5.188.206.0/24', description: 'Spamhaus DROP - spam/botnet' }, - { cidr: '23.94.0.0/15', description: 'Known bulletproof hosting' }, - { cidr: '31.13.195.0/24', description: 'Spamhaus EDROP - malware' }, - { cidr: '45.14.224.0/22', description: 'Abuse.ch - malware hosting' }, - { cidr: '77.247.110.0/24', description: 'Known C&C servers' }, - { cidr: '91.200.12.0/22', description: 'Spamhaus DROP - botnet' }, - { cidr: '91.211.116.0/22', description: 'Known spam origin' }, - { cidr: '185.234.216.0/22', description: 'Abuse.ch - botnet infrastructure' }, - { cidr: '194.165.16.0/23', description: 'Known attack infrastructure' }, - ], - estimatedIPs: '~50,000', - dataSource: 'Spamhaus DROP/EDROP + abuse.ch', - dataSourceUrl: 'https://www.spamhaus.org/drop/', - }, - { - id: 'cloud-scanners', - name: 'Block Cloud Scanner IPs', - description: 'Block IP ranges used by mass scanning services (Shodan, Censys, etc.)', - category: 'security', - type: 'blacklist', - ipRanges: [ - // Shodan scanning IPs - { cidr: '71.6.135.0/24', description: 'Shodan scanners' }, - { cidr: '71.6.167.0/24', description: 'Shodan scanners' }, - { cidr: '82.221.105.0/24', description: 'Shodan scanners' }, - { cidr: '85.25.43.0/24', description: 'Shodan scanners' }, - { cidr: '85.25.103.0/24', description: 'Shodan scanners' }, - { cidr: '93.120.27.0/24', description: 'Shodan scanners' }, - { cidr: '198.108.66.0/24', description: 'Shodan scanners' }, - { cidr: '198.20.69.0/24', description: 'Shodan scanners' }, - // Censys scanning IPs - { cidr: '162.142.125.0/24', description: 'Censys scanners' }, - { cidr: '167.248.133.0/24', description: 'Censys scanners' }, - { cidr: '167.94.138.0/24', description: 'Censys scanners' }, - { cidr: '167.94.145.0/24', description: 'Censys scanners' }, - { cidr: '167.94.146.0/24', description: 'Censys scanners' }, - // SecurityTrails/BinaryEdge - { cidr: '45.33.32.0/24', description: 'Security scanners' }, - { cidr: '45.33.34.0/24', description: 'Security scanners' }, - ], - estimatedIPs: '~4,000', - dataSource: 'Shodan/Censys official scanner lists', - dataSourceUrl: 'https://help.shodan.io/the-basics/what-is-shodan', - warning: 'Blocks known scanner IPs. New scanners may not be included.', - }, - { - id: 'tor-exit-nodes', - name: 'Block Tor Exit Nodes', - description: 'Block known Tor network exit nodes to prevent anonymous access', - category: 'advanced', - type: 'blacklist', - ipRanges: [ - // Tor exit node ranges (subset - changes frequently) - { cidr: '185.220.100.0/22', description: 'Tor exit nodes' }, - { cidr: '185.220.101.0/24', description: 'Tor exit nodes' }, - { cidr: '185.220.102.0/24', description: 'Tor exit nodes' }, - { cidr: '185.100.84.0/22', description: 'Tor exit nodes' }, - { cidr: '185.100.86.0/24', description: 'Tor exit nodes' }, - { cidr: '185.100.87.0/24', description: 'Tor exit nodes' }, - { cidr: '176.10.99.0/24', description: 'Tor exit nodes' }, - { cidr: '176.10.104.0/22', description: 'Tor exit nodes' }, - { cidr: '51.15.0.0/16', description: 'Scaleway - common Tor hosting' }, - ], - estimatedIPs: '~70,000', - dataSource: 'Tor Project Exit Node List', - dataSourceUrl: 'https://check.torproject.org/exit-addresses', - warning: 'Tor exit nodes change frequently. List may be incomplete.', - }, - { - id: 'vpn-datacenter-ips', - name: 'Block VPN & Datacenter IPs', - description: 'Block known VPN providers and datacenter IP ranges commonly used for abuse', - category: 'advanced', - type: 'blacklist', - ipRanges: [ - // Common VPN/Datacenter ranges used for abuse - { cidr: '104.238.128.0/17', description: 'Vultr hosting - common VPN' }, - { cidr: '45.77.0.0/16', description: 'Vultr hosting' }, - { cidr: '66.42.32.0/19', description: 'Choopa/Vultr' }, - { cidr: '149.28.0.0/16', description: 'Vultr Japan/Singapore' }, - { cidr: '155.138.128.0/17', description: 'Vultr hosting' }, - { cidr: '207.148.64.0/18', description: 'Vultr hosting' }, - { cidr: '209.250.224.0/19', description: 'Vultr hosting' }, - ], - estimatedIPs: '~600,000', - dataSource: 'Known VPN/Datacenter ranges', - dataSourceUrl: 'https://www.vultr.com/resources/faq/', - warning: 'May block legitimate VPN users. Use with caution.', - }, - { - id: 'scraper-bots', - name: 'Block Web Scraper Bots', - description: 'Block known aggressive web scraping services and bad bots', - category: 'advanced', - type: 'blacklist', - ipRanges: [ - // Aggressive scrapers - { cidr: '35.192.0.0/12', description: 'GCP - common scraper hosting' }, - { cidr: '54.208.0.0/13', description: 'AWS us-east - scraper hosting' }, - { cidr: '13.32.0.0/12', description: 'AWS CloudFront - may be scrapers' }, - { cidr: '18.188.0.0/14', description: 'AWS us-east-2 - known bots' }, - // Known bad bot operators - { cidr: '216.244.66.0/24', description: 'DotBot scraper' }, - { cidr: '46.4.122.0/24', description: 'MJ12bot scraper' }, - { cidr: '144.76.38.0/24', description: 'SEMrush bot' }, - { cidr: '46.229.168.0/24', description: 'BLEXBot scraper' }, - ], - estimatedIPs: '~4 million', - dataSource: 'Bad bot IP feeds', - dataSourceUrl: 'https://radar.cloudflare.com/traffic/bots', - warning: 'Blocks large cloud ranges. May impact legitimate services.', - }, ]; export const getPresetById = (id: string): SecurityPreset | undefined => { diff --git a/frontend/src/pages/ProxyHosts.tsx b/frontend/src/pages/ProxyHosts.tsx index e7079e11..3f3fbdaf 100644 --- a/frontend/src/pages/ProxyHosts.tsx +++ b/frontend/src/pages/ProxyHosts.tsx @@ -510,6 +510,11 @@ export default function ProxyHosts() { WS )} + {host.access_list_id && ( + + ACL + + )}
{host.certificate && host.certificate.provider === 'custom' && (
@@ -609,6 +614,9 @@ export default function ProxyHosts() {

Applying to {selectedHosts.size} selected host(s)

+

+ Note: Each proxy host can have a single Access Control List applied. Selecting multiple lists will apply them sequentially and the last applied list will be the effective one for each host. +

{/* Action Toggle */}
diff --git a/frontend/src/pages/__tests__/Security.spec.tsx b/frontend/src/pages/__tests__/Security.spec.tsx index ee5ff554..62fdeaf5 100644 --- a/frontend/src/pages/__tests__/Security.spec.tsx +++ b/frontend/src/pages/__tests__/Security.spec.tsx @@ -76,6 +76,23 @@ describe('Security page', () => { expect(screen.queryByTestId('enable-all-btn')).toBeNull() }) + it('calls updateSetting when toggling ACL', async () => { + const status: SecurityStatus = { + cerberus: { enabled: true }, + crowdsec: { enabled: false, mode: 'disabled' as const, api_url: '' }, + waf: { enabled: false, mode: 'disabled' as const }, + rate_limit: { enabled: false }, + acl: { enabled: false }, + } + vi.mocked(api.getSecurityStatus).mockResolvedValue(status as SecurityStatus) + const updateSpy = vi.mocked(settingsApi.updateSetting) + renderWithProviders() + await waitFor(() => expect(screen.getByText('Security Dashboard')).toBeInTheDocument()) + const aclToggle = screen.getByTestId('toggle-acl') + await userEvent.click(aclToggle) + await waitFor(() => expect(updateSpy).toHaveBeenCalledWith('security.acl.enabled', 'true', 'security', 'bool')) + }) + it('calls export endpoint when clicking Export', async () => { const status: SecurityStatus = { cerberus: { enabled: true }, diff --git a/frontend/src/types/testing-library-user-event.d.ts b/frontend/src/types/testing-library-user-event.d.ts new file mode 100644 index 00000000..e5990973 --- /dev/null +++ b/frontend/src/types/testing-library-user-event.d.ts @@ -0,0 +1,5 @@ +// ambient module declaration to satisfy typescript in editor environment +declare module '@testing-library/user-event' { + const userEvent: any; + export default userEvent; +}