chore: clean git cache
This commit is contained in:
306
frontend/src/data/__tests__/crowdsecPresets.test.ts
Normal file
306
frontend/src/data/__tests__/crowdsecPresets.test.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { CROWDSEC_PRESETS, findCrowdsecPreset, type CrowdsecPreset } from '../crowdsecPresets'
|
||||
|
||||
describe('crowdsecPresets', () => {
|
||||
describe('CROWDSEC_PRESETS', () => {
|
||||
it('should contain all expected presets', () => {
|
||||
expect(CROWDSEC_PRESETS).toHaveLength(3)
|
||||
expect(CROWDSEC_PRESETS.map((p) => p.slug)).toEqual([
|
||||
'bot-mitigation-essentials',
|
||||
'honeypot-friendly-defaults',
|
||||
'geolocation-aware',
|
||||
])
|
||||
})
|
||||
|
||||
it('should have valid YAML content for each preset', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.content).toContain('configs:')
|
||||
expect(preset.content).toMatch(/collections:|parsers:|scenarios:|postoverflows:/)
|
||||
})
|
||||
})
|
||||
|
||||
it('should have required metadata fields', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset).toHaveProperty('slug')
|
||||
expect(preset).toHaveProperty('title')
|
||||
expect(preset).toHaveProperty('description')
|
||||
expect(preset).toHaveProperty('content')
|
||||
expect(preset.slug).toMatch(/^[a-z0-9-]+$/) // Slug format validation
|
||||
})
|
||||
})
|
||||
|
||||
it('should have descriptive titles and descriptions', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.title.length).toBeGreaterThan(5)
|
||||
expect(preset.description.length).toBeGreaterThan(10)
|
||||
})
|
||||
})
|
||||
|
||||
it('should have tags for each preset', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.tags).toBeDefined()
|
||||
expect(Array.isArray(preset.tags)).toBe(true)
|
||||
expect(preset.tags!.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('should have warnings for production-critical presets', () => {
|
||||
const botMitigation = CROWDSEC_PRESETS.find((p) => p.slug === 'bot-mitigation-essentials')
|
||||
expect(botMitigation?.warning).toBeDefined()
|
||||
expect(botMitigation?.warning).toContain('allowlist')
|
||||
|
||||
const honeypot = CROWDSEC_PRESETS.find((p) => p.slug === 'honeypot-friendly-defaults')
|
||||
expect(honeypot?.warning).toBeDefined()
|
||||
expect(honeypot?.warning).toContain('honeypot')
|
||||
|
||||
const geo = CROWDSEC_PRESETS.find((p) => p.slug === 'geolocation-aware')
|
||||
expect(geo?.warning).toBeDefined()
|
||||
expect(geo?.warning).toContain('GeoIP')
|
||||
})
|
||||
})
|
||||
|
||||
describe('preset content integrity', () => {
|
||||
it('should have valid CrowdSec YAML structure', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
const lines = preset.content.split('\n')
|
||||
expect(lines[0]).toMatch(/^configs:/)
|
||||
})
|
||||
})
|
||||
|
||||
it('should reference valid CrowdSec hub items', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
// Extract collection references
|
||||
const collections = preset.content.match(/- crowdsecurity\/[\w-]+/g) || []
|
||||
collections.forEach((item) => {
|
||||
// Hub items can contain underscores (e.g., http-crawl-non_statics)
|
||||
expect(item).toMatch(/^- crowdsecurity\/[a-z0-9-_]+$/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should have proper YAML indentation', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
const lines = preset.content.split('\n')
|
||||
// Check that collection/parser/scenario items are indented with spaces
|
||||
const itemLines = lines.filter((line) => line.trim().startsWith('- crowdsecurity/'))
|
||||
itemLines.forEach((line) => {
|
||||
expect(line).toMatch(/^\s{4,}- crowdsecurity\//)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should reference known CrowdSec collections', () => {
|
||||
const botMitigation = CROWDSEC_PRESETS.find((p) => p.slug === 'bot-mitigation-essentials')
|
||||
expect(botMitigation?.content).toContain('crowdsecurity/base-http-scenarios')
|
||||
expect(botMitigation?.content).toContain('crowdsecurity/http-cve')
|
||||
})
|
||||
|
||||
it('should reference known CrowdSec parsers', () => {
|
||||
const botMitigation = CROWDSEC_PRESETS.find((p) => p.slug === 'bot-mitigation-essentials')
|
||||
expect(botMitigation?.content).toContain('crowdsecurity/http-logs')
|
||||
expect(botMitigation?.content).toContain('crowdsecurity/nginx-logs')
|
||||
})
|
||||
|
||||
it('should reference known CrowdSec scenarios', () => {
|
||||
const botMitigation = CROWDSEC_PRESETS.find((p) => p.slug === 'bot-mitigation-essentials')
|
||||
expect(botMitigation?.content).toContain('crowdsecurity/http-bf')
|
||||
expect(botMitigation?.content).toContain('crowdsecurity/http-probing')
|
||||
})
|
||||
|
||||
it('should have whitelists postoverflow for production presets', () => {
|
||||
const botMitigation = CROWDSEC_PRESETS.find((p) => p.slug === 'bot-mitigation-essentials')
|
||||
expect(botMitigation?.content).toContain('postoverflows:')
|
||||
expect(botMitigation?.content).toContain('crowdsecurity/whitelists')
|
||||
})
|
||||
})
|
||||
|
||||
describe('findCrowdsecPreset', () => {
|
||||
it('should find preset by slug', () => {
|
||||
const preset = findCrowdsecPreset('bot-mitigation-essentials')
|
||||
expect(preset).toBeDefined()
|
||||
expect(preset?.slug).toBe('bot-mitigation-essentials')
|
||||
expect(preset?.title).toBe('Bot Mitigation Essentials')
|
||||
})
|
||||
|
||||
it('should find honeypot preset', () => {
|
||||
const preset = findCrowdsecPreset('honeypot-friendly-defaults')
|
||||
expect(preset).toBeDefined()
|
||||
expect(preset?.slug).toBe('honeypot-friendly-defaults')
|
||||
expect(preset?.tags).toContain('low-noise')
|
||||
})
|
||||
|
||||
it('should find geolocation preset', () => {
|
||||
const preset = findCrowdsecPreset('geolocation-aware')
|
||||
expect(preset).toBeDefined()
|
||||
expect(preset?.slug).toBe('geolocation-aware')
|
||||
expect(preset?.tags).toContain('geo')
|
||||
})
|
||||
|
||||
it('should return undefined for non-existent slug', () => {
|
||||
const preset = findCrowdsecPreset('non-existent-preset')
|
||||
expect(preset).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should be case-sensitive', () => {
|
||||
const preset = findCrowdsecPreset('BOT-MITIGATION-ESSENTIALS')
|
||||
expect(preset).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should not match partial slugs', () => {
|
||||
const preset = findCrowdsecPreset('bot-mitigation')
|
||||
expect(preset).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const preset = findCrowdsecPreset('')
|
||||
expect(preset).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle slugs with special characters', () => {
|
||||
const preset = findCrowdsecPreset('bot/mitigation')
|
||||
expect(preset).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('preset-specific validations', () => {
|
||||
it('bot-mitigation-essentials should target web threats', () => {
|
||||
const preset = findCrowdsecPreset('bot-mitigation-essentials')
|
||||
expect(preset?.tags).toContain('bots')
|
||||
expect(preset?.tags).toContain('web')
|
||||
expect(preset?.tags).toContain('auth')
|
||||
expect(preset?.description).toContain('credential stuffing')
|
||||
expect(preset?.description).toContain('scanners')
|
||||
})
|
||||
|
||||
it('honeypot-friendly-defaults should be low-noise', () => {
|
||||
const preset = findCrowdsecPreset('honeypot-friendly-defaults')
|
||||
expect(preset?.tags).toContain('low-noise')
|
||||
expect(preset?.tags).toContain('ssh')
|
||||
expect(preset?.description).toContain('honeypot')
|
||||
expect(preset?.content).toContain('crowdsecurity/sshd')
|
||||
})
|
||||
|
||||
it('geolocation-aware should require GeoIP', () => {
|
||||
const preset = findCrowdsecPreset('geolocation-aware')
|
||||
expect(preset?.tags).toContain('geo')
|
||||
expect(preset?.tags).toContain('access-control')
|
||||
expect(preset?.warning).toContain('GeoIP database')
|
||||
expect(preset?.content).toContain('geoip-enricher')
|
||||
})
|
||||
})
|
||||
|
||||
describe('preset tag consistency', () => {
|
||||
it('should have consistent tag naming (lowercase, hyphenated)', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
preset.tags?.forEach((tag) => {
|
||||
expect(tag).toMatch(/^[a-z0-9-]+$/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should have descriptive tags', () => {
|
||||
const allTags = CROWDSEC_PRESETS.flatMap((p) => p.tags || [])
|
||||
expect(allTags.length).toBeGreaterThan(5)
|
||||
expect(new Set(allTags).size).toBeGreaterThan(3) // Multiple unique tags
|
||||
})
|
||||
})
|
||||
|
||||
describe('slug format validation', () => {
|
||||
it('should use lowercase slugs', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.slug).toBe(preset.slug.toLowerCase())
|
||||
})
|
||||
})
|
||||
|
||||
it('should use hyphens as separators', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.slug).not.toContain('_')
|
||||
expect(preset.slug).not.toContain(' ')
|
||||
})
|
||||
})
|
||||
|
||||
it('should not have leading or trailing hyphens', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.slug).not.toMatch(/^-/)
|
||||
expect(preset.slug).not.toMatch(/-$/)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not have consecutive hyphens', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.slug).not.toContain('--')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('content safety', () => {
|
||||
it('should not contain executable code', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.content).not.toContain('<script')
|
||||
expect(preset.content).not.toContain('eval(')
|
||||
expect(preset.content).not.toContain('exec(')
|
||||
})
|
||||
})
|
||||
|
||||
it('should not contain SQL injection attempts', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.content).not.toContain('DROP TABLE')
|
||||
expect(preset.content).not.toContain('DELETE FROM')
|
||||
})
|
||||
})
|
||||
|
||||
it('should not contain path traversal attempts', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.content).not.toContain('../')
|
||||
expect(preset.content).not.toContain('..\\')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('TypeScript type safety', () => {
|
||||
it('should satisfy CrowdsecPreset interface', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
const typedPreset: CrowdsecPreset = preset
|
||||
expect(typedPreset.slug).toBeTruthy()
|
||||
expect(typedPreset.title).toBeTruthy()
|
||||
expect(typedPreset.description).toBeTruthy()
|
||||
expect(typedPreset.content).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
it('should have optional tags and warning properties', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
if (preset.tags !== undefined) {
|
||||
expect(Array.isArray(preset.tags)).toBe(true)
|
||||
}
|
||||
if (preset.warning !== undefined) {
|
||||
expect(typeof preset.warning).toBe('string')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('usability validations', () => {
|
||||
it('should have human-readable titles', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.title).not.toMatch(/^[a-z-]+$/) // Not just slug format
|
||||
expect(preset.title).toMatch(/^[A-Z]/) // Starts with capital
|
||||
})
|
||||
})
|
||||
|
||||
it('should have actionable descriptions', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
expect(preset.description.split(' ').length).toBeGreaterThan(5)
|
||||
})
|
||||
})
|
||||
|
||||
it('should have clear warnings when present', () => {
|
||||
CROWDSEC_PRESETS.forEach((preset) => {
|
||||
if (preset.warning) {
|
||||
expect(preset.warning.length).toBeGreaterThan(10)
|
||||
expect(preset.warning).toMatch(/[.!]$/) // Ends with punctuation
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
165
frontend/src/data/__tests__/securityPresets.test.ts
Normal file
165
frontend/src/data/__tests__/securityPresets.test.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
SECURITY_PRESETS,
|
||||
getPresetById,
|
||||
getPresetsByCategory,
|
||||
calculateCIDRSize,
|
||||
formatIPCount,
|
||||
calculateTotalIPs,
|
||||
} from '../securityPresets';
|
||||
|
||||
describe('securityPresets', () => {
|
||||
describe('SECURITY_PRESETS', () => {
|
||||
it('contains expected presets', () => {
|
||||
expect(SECURITY_PRESETS.length).toBeGreaterThan(0);
|
||||
|
||||
// Verify preset structure
|
||||
SECURITY_PRESETS.forEach((preset) => {
|
||||
expect(preset).toHaveProperty('id');
|
||||
expect(preset).toHaveProperty('name');
|
||||
expect(preset).toHaveProperty('description');
|
||||
expect(preset).toHaveProperty('category');
|
||||
expect(preset).toHaveProperty('type');
|
||||
expect(preset).toHaveProperty('estimatedIPs');
|
||||
expect(preset).toHaveProperty('dataSource');
|
||||
expect(preset).toHaveProperty('dataSourceUrl');
|
||||
});
|
||||
});
|
||||
|
||||
it('has valid categories', () => {
|
||||
const validCategories = ['security', 'advanced'];
|
||||
SECURITY_PRESETS.forEach((preset) => {
|
||||
expect(validCategories).toContain(preset.category);
|
||||
});
|
||||
});
|
||||
|
||||
it('has valid types', () => {
|
||||
const validTypes = ['geo_blacklist', 'blacklist'];
|
||||
SECURITY_PRESETS.forEach((preset) => {
|
||||
expect(validTypes).toContain(preset.type);
|
||||
});
|
||||
});
|
||||
|
||||
it('geo_blacklist presets have countryCodes', () => {
|
||||
const geoPresets = SECURITY_PRESETS.filter((p) => p.type === 'geo_blacklist');
|
||||
geoPresets.forEach((preset) => {
|
||||
expect(preset.countryCodes).toBeDefined();
|
||||
expect(preset.countryCodes!.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('no IP-based blacklist presets are included (CrowdSec handles dynamic IP threats)', () => {
|
||||
const ipPresets = SECURITY_PRESETS.filter((p) => p.type === 'blacklist');
|
||||
// IP-based blacklists are deprecated and should be handled by CrowdSec / WAF / rate limiting
|
||||
expect(ipPresets.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPresetById', () => {
|
||||
it('returns preset when found', () => {
|
||||
const preset = getPresetById('high-risk-countries');
|
||||
expect(preset).toBeDefined();
|
||||
expect(preset?.id).toBe('high-risk-countries');
|
||||
expect(preset?.name).toBe('Block High-Risk Countries');
|
||||
});
|
||||
|
||||
it('returns undefined when not found', () => {
|
||||
const preset = getPresetById('nonexistent-preset');
|
||||
expect(preset).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPresetsByCategory', () => {
|
||||
it('returns security category presets', () => {
|
||||
const securityPresets = getPresetsByCategory('security');
|
||||
expect(securityPresets.length).toBeGreaterThan(0);
|
||||
securityPresets.forEach((preset) => {
|
||||
expect(preset.category).toBe('security');
|
||||
});
|
||||
});
|
||||
|
||||
it('returns advanced category presets (may be empty)', () => {
|
||||
const advancedPresets = getPresetsByCategory('advanced');
|
||||
expect(Array.isArray(advancedPresets)).toBe(true);
|
||||
advancedPresets.forEach((preset) => {
|
||||
expect(preset.category).toBe('advanced');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateCIDRSize', () => {
|
||||
it('calculates /32 as 1 IP', () => {
|
||||
expect(calculateCIDRSize('192.168.1.1/32')).toBe(1);
|
||||
});
|
||||
|
||||
it('calculates /24 as 256 IPs', () => {
|
||||
expect(calculateCIDRSize('192.168.1.0/24')).toBe(256);
|
||||
});
|
||||
|
||||
it('calculates /16 as 65536 IPs', () => {
|
||||
expect(calculateCIDRSize('192.168.0.0/16')).toBe(65536);
|
||||
});
|
||||
|
||||
it('calculates /8 as 16777216 IPs', () => {
|
||||
expect(calculateCIDRSize('10.0.0.0/8')).toBe(16777216);
|
||||
});
|
||||
|
||||
it('calculates /0 as all IPs', () => {
|
||||
expect(calculateCIDRSize('0.0.0.0/0')).toBe(4294967296);
|
||||
});
|
||||
|
||||
it('returns 1 for single IP without CIDR notation', () => {
|
||||
expect(calculateCIDRSize('192.168.1.1')).toBe(1);
|
||||
});
|
||||
|
||||
it('returns 1 for invalid CIDR', () => {
|
||||
expect(calculateCIDRSize('invalid')).toBe(1);
|
||||
expect(calculateCIDRSize('192.168.1.0/abc')).toBe(1);
|
||||
expect(calculateCIDRSize('192.168.1.0/-1')).toBe(1);
|
||||
expect(calculateCIDRSize('192.168.1.0/33')).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatIPCount', () => {
|
||||
it('formats small numbers as-is', () => {
|
||||
expect(formatIPCount(0)).toBe('0');
|
||||
expect(formatIPCount(1)).toBe('1');
|
||||
expect(formatIPCount(999)).toBe('999');
|
||||
});
|
||||
|
||||
it('formats thousands with K suffix', () => {
|
||||
expect(formatIPCount(1000)).toBe('1.0K');
|
||||
expect(formatIPCount(1500)).toBe('1.5K');
|
||||
expect(formatIPCount(999999)).toBe('1000.0K');
|
||||
});
|
||||
|
||||
it('formats millions with M suffix', () => {
|
||||
expect(formatIPCount(1000000)).toBe('1.0M');
|
||||
expect(formatIPCount(2500000)).toBe('2.5M');
|
||||
expect(formatIPCount(999999999)).toBe('1000.0M');
|
||||
});
|
||||
|
||||
it('formats billions with B suffix', () => {
|
||||
expect(formatIPCount(1000000000)).toBe('1.0B');
|
||||
expect(formatIPCount(4294967296)).toBe('4.3B');
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateTotalIPs', () => {
|
||||
it('calculates total for single CIDR', () => {
|
||||
expect(calculateTotalIPs(['192.168.1.0/24'])).toBe(256);
|
||||
});
|
||||
|
||||
it('calculates total for multiple CIDRs', () => {
|
||||
expect(calculateTotalIPs(['192.168.1.0/24', '10.0.0.0/24'])).toBe(512);
|
||||
});
|
||||
|
||||
it('handles empty array', () => {
|
||||
expect(calculateTotalIPs([])).toBe(0);
|
||||
});
|
||||
|
||||
it('handles mixed valid and invalid CIDRs', () => {
|
||||
expect(calculateTotalIPs(['192.168.1.0/24', 'invalid'])).toBe(257);
|
||||
});
|
||||
});
|
||||
});
|
||||
77
frontend/src/data/crowdsecPresets.ts
Normal file
77
frontend/src/data/crowdsecPresets.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
export interface CrowdsecPreset {
|
||||
slug: string
|
||||
title: string
|
||||
description: string
|
||||
content: string
|
||||
tags?: string[]
|
||||
warning?: string
|
||||
}
|
||||
|
||||
export const CROWDSEC_PRESETS: CrowdsecPreset[] = [
|
||||
{
|
||||
slug: 'bot-mitigation-essentials',
|
||||
title: 'Bot Mitigation Essentials',
|
||||
description:
|
||||
'Core HTTP parsers and scenarios aimed at credential stuffing, scanners, and bad crawlers with minimal false positives.',
|
||||
tags: ['bots', 'web', 'auth'],
|
||||
content: `configs:
|
||||
collections:
|
||||
- crowdsecurity/base-http-scenarios
|
||||
- crowdsecurity/http-cve
|
||||
- crowdsecurity/http-bad-user-agent
|
||||
parsers:
|
||||
- crowdsecurity/http-logs
|
||||
- crowdsecurity/nginx-logs
|
||||
- crowdsecurity/apache2-logs
|
||||
scenarios:
|
||||
- crowdsecurity/http-bf
|
||||
- crowdsecurity/http-sensitive-files
|
||||
- crowdsecurity/http-probing
|
||||
- crowdsecurity/http-crawl-non_statics
|
||||
postoverflows:
|
||||
- crowdsecurity/whitelists
|
||||
`,
|
||||
warning: 'Best for internet-facing apps; ensure allowlists cover SSO and monitoring probes.',
|
||||
},
|
||||
{
|
||||
slug: 'honeypot-friendly-defaults',
|
||||
title: 'Honeypot Friendly Defaults',
|
||||
description: 'Lightweight defaults tuned for tarpits and research honeypots to reduce noisy bans.',
|
||||
tags: ['low-noise', 'ssh', 'http'],
|
||||
content: `configs:
|
||||
collections:
|
||||
- crowdsecurity/sshd
|
||||
- crowdsecurity/caddy
|
||||
parsers:
|
||||
- crowdsecurity/sshd-logs
|
||||
- crowdsecurity/caddy-logs
|
||||
scenarios:
|
||||
- crowdsecurity/ssh-bf
|
||||
- crowdsecurity/http-backdoors-attempts
|
||||
- crowdsecurity/http-probing
|
||||
postoverflows:
|
||||
- crowdsecurity/whitelists
|
||||
`,
|
||||
warning: 'Keep honeypot endpoints isolated; avoid applying to production ingress.',
|
||||
},
|
||||
{
|
||||
slug: 'geolocation-aware',
|
||||
title: 'Geolocation Aware',
|
||||
description: 'Adds geo-enrichment and region-aware scenarios to tighten access by country.',
|
||||
tags: ['geo', 'access-control'],
|
||||
content: `configs:
|
||||
collections:
|
||||
- crowdsecurity/geoip-enricher
|
||||
scenarios:
|
||||
- crowdsecurity/geo-fencing
|
||||
- crowdsecurity/geo-bf
|
||||
postoverflows:
|
||||
- crowdsecurity/whitelists
|
||||
`,
|
||||
warning: 'Requires GeoIP database. Pair with ACLs to avoid blocking legitimate traffic.',
|
||||
},
|
||||
]
|
||||
|
||||
export const findCrowdsecPreset = (slug: string): CrowdsecPreset | undefined => {
|
||||
return CROWDSEC_PRESETS.find((preset) => preset.slug === slug)
|
||||
}
|
||||
208
frontend/src/data/dnsProviderSchemas.ts
Normal file
208
frontend/src/data/dnsProviderSchemas.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import type { DNSProviderType, DNSProviderTypeInfo } from '../api/dnsProviders'
|
||||
|
||||
/**
|
||||
* Default provider field schemas.
|
||||
* These are fallback definitions; actual field definitions come from the API.
|
||||
*/
|
||||
export const defaultProviderSchemas: Record<DNSProviderType, Partial<DNSProviderTypeInfo>> = {
|
||||
cloudflare: {
|
||||
type: 'cloudflare',
|
||||
name: 'Cloudflare',
|
||||
fields: [
|
||||
{
|
||||
name: 'api_token',
|
||||
label: 'API Token',
|
||||
type: 'password',
|
||||
required: true,
|
||||
hint: 'Token with Zone:DNS:Edit permissions',
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://developers.cloudflare.com/api/tokens/',
|
||||
},
|
||||
route53: {
|
||||
type: 'route53',
|
||||
name: 'Amazon Route 53',
|
||||
fields: [
|
||||
{
|
||||
name: 'access_key_id',
|
||||
label: 'Access Key ID',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'secret_access_key',
|
||||
label: 'Secret Access Key',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'region',
|
||||
label: 'AWS Region',
|
||||
type: 'text',
|
||||
required: true,
|
||||
default: 'us-east-1',
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://docs.aws.amazon.com/Route53/',
|
||||
},
|
||||
digitalocean: {
|
||||
type: 'digitalocean',
|
||||
name: 'DigitalOcean',
|
||||
fields: [
|
||||
{
|
||||
name: 'auth_token',
|
||||
label: 'Auth Token',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://docs.digitalocean.com/reference/api/',
|
||||
},
|
||||
googleclouddns: {
|
||||
type: 'googleclouddns',
|
||||
name: 'Google Cloud DNS',
|
||||
fields: [
|
||||
{
|
||||
name: 'service_account_json',
|
||||
label: 'Service Account JSON',
|
||||
type: 'password',
|
||||
required: true,
|
||||
hint: 'Paste the entire JSON file contents',
|
||||
},
|
||||
{
|
||||
name: 'project',
|
||||
label: 'Project ID',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://cloud.google.com/dns/docs',
|
||||
},
|
||||
namecheap: {
|
||||
type: 'namecheap',
|
||||
name: 'Namecheap',
|
||||
fields: [
|
||||
{
|
||||
name: 'api_user',
|
||||
label: 'API User',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'api_key',
|
||||
label: 'API Key',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'client_ip',
|
||||
label: 'Client IP',
|
||||
type: 'text',
|
||||
required: true,
|
||||
hint: 'Your whitelisted IP address',
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://www.namecheap.com/support/api/',
|
||||
},
|
||||
godaddy: {
|
||||
type: 'godaddy',
|
||||
name: 'GoDaddy',
|
||||
fields: [
|
||||
{
|
||||
name: 'api_key',
|
||||
label: 'API Key',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'api_secret',
|
||||
label: 'API Secret',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://developer.godaddy.com/',
|
||||
},
|
||||
azure: {
|
||||
type: 'azure',
|
||||
name: 'Azure DNS',
|
||||
fields: [
|
||||
{
|
||||
name: 'tenant_id',
|
||||
label: 'Tenant ID',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'client_id',
|
||||
label: 'Client ID',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'client_secret',
|
||||
label: 'Client Secret',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'subscription_id',
|
||||
label: 'Subscription ID',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'resource_group',
|
||||
label: 'Resource Group',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://learn.microsoft.com/en-us/azure/dns/',
|
||||
},
|
||||
hetzner: {
|
||||
type: 'hetzner',
|
||||
name: 'Hetzner',
|
||||
fields: [
|
||||
{
|
||||
name: 'api_key',
|
||||
label: 'API Key',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://dns.hetzner.com/api-docs',
|
||||
},
|
||||
vultr: {
|
||||
type: 'vultr',
|
||||
name: 'Vultr',
|
||||
fields: [
|
||||
{
|
||||
name: 'api_key',
|
||||
label: 'API Key',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://www.vultr.com/api/',
|
||||
},
|
||||
dnsimple: {
|
||||
type: 'dnsimple',
|
||||
name: 'DNSimple',
|
||||
fields: [
|
||||
{
|
||||
name: 'oauth_token',
|
||||
label: 'OAuth Token',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'account_id',
|
||||
label: 'Account ID',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
documentation_url: 'https://developer.dnsimple.com/',
|
||||
},
|
||||
}
|
||||
124
frontend/src/data/securityPresets.ts
Normal file
124
frontend/src/data/securityPresets.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Security Presets for Access Control Lists
|
||||
*
|
||||
* Data sources:
|
||||
* - High-risk countries: Based on common attack origin statistics from threat intelligence feeds
|
||||
* - Cloud scanner IPs: Known IP ranges used for mass scanning (Shodan, Censys, etc.)
|
||||
* - Botnet IPs: Curated from public blocklists (Spamhaus, abuse.ch, etc.)
|
||||
*
|
||||
* References:
|
||||
* - SANS Internet Storm Center: https://isc.sans.edu/
|
||||
* - Spamhaus DROP/EDROP lists: https://www.spamhaus.org/drop/
|
||||
* - Abuse.ch threat feeds: https://abuse.ch/
|
||||
*/
|
||||
|
||||
export interface SecurityPreset {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: 'security' | 'advanced';
|
||||
type: 'geo_blacklist' | 'blacklist';
|
||||
countryCodes?: string[];
|
||||
ipRanges?: Array<{ cidr: string; description: string }>;
|
||||
estimatedIPs: string;
|
||||
dataSource: string;
|
||||
dataSourceUrl: string;
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
export const SECURITY_PRESETS: SecurityPreset[] = [
|
||||
{
|
||||
id: 'high-risk-countries',
|
||||
name: 'Block High-Risk Countries',
|
||||
description: 'Block countries with highest attack/spam rates (OFAC sanctioned + known attack sources)',
|
||||
category: 'security',
|
||||
type: 'geo_blacklist',
|
||||
countryCodes: [
|
||||
'RU', // Russia
|
||||
'CN', // China
|
||||
'KP', // North Korea
|
||||
'IR', // Iran
|
||||
'BY', // Belarus
|
||||
'SY', // Syria
|
||||
'VE', // Venezuela
|
||||
'CU', // Cuba
|
||||
'SD', // Sudan
|
||||
],
|
||||
estimatedIPs: '~800 million',
|
||||
dataSource: 'SANS ISC Top Attack Origins',
|
||||
dataSourceUrl: 'https://isc.sans.edu/sources.html',
|
||||
warning: 'This blocks entire countries. Legitimate users from these countries will be blocked.',
|
||||
},
|
||||
{
|
||||
id: 'expanded-threat-countries',
|
||||
name: 'Block Expanded Threat List',
|
||||
description: 'High-risk countries plus additional sources of bot traffic and spam',
|
||||
category: 'security',
|
||||
type: 'geo_blacklist',
|
||||
countryCodes: [
|
||||
'RU', // Russia
|
||||
'CN', // China
|
||||
'KP', // North Korea
|
||||
'IR', // Iran
|
||||
'BY', // Belarus
|
||||
'SY', // Syria
|
||||
'VE', // Venezuela
|
||||
'CU', // Cuba
|
||||
'SD', // Sudan
|
||||
'PK', // Pakistan
|
||||
'BD', // Bangladesh
|
||||
'NG', // Nigeria
|
||||
'UA', // Ukraine (high bot activity)
|
||||
'VN', // Vietnam
|
||||
'ID', // Indonesia
|
||||
],
|
||||
estimatedIPs: '~1.2 billion',
|
||||
dataSource: 'Combined threat intelligence feeds',
|
||||
dataSourceUrl: 'https://isc.sans.edu/',
|
||||
warning: 'Aggressive blocking. May impact legitimate international users.',
|
||||
},
|
||||
];
|
||||
|
||||
export const getPresetById = (id: string): SecurityPreset | undefined => {
|
||||
return SECURITY_PRESETS.find((preset) => preset.id === id);
|
||||
};
|
||||
|
||||
export const getPresetsByCategory = (category: 'security' | 'advanced'): SecurityPreset[] => {
|
||||
return SECURITY_PRESETS.filter((preset) => preset.category === category);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate approximate number of IPs in a CIDR range
|
||||
*/
|
||||
export const calculateCIDRSize = (cidr: string): number => {
|
||||
const parts = cidr.split('/');
|
||||
if (parts.length !== 2) return 1;
|
||||
|
||||
const bits = parseInt(parts[1], 10);
|
||||
if (isNaN(bits) || bits < 0 || bits > 32) return 1;
|
||||
|
||||
return Math.pow(2, 32 - bits);
|
||||
};
|
||||
|
||||
/**
|
||||
* Format IP count for display
|
||||
*/
|
||||
export const formatIPCount = (count: number): string => {
|
||||
if (count >= 1000000000) {
|
||||
return `${(count / 1000000000).toFixed(1)}B`;
|
||||
}
|
||||
if (count >= 1000000) {
|
||||
return `${(count / 1000000).toFixed(1)}M`;
|
||||
}
|
||||
if (count >= 1000) {
|
||||
return `${(count / 1000).toFixed(1)}K`;
|
||||
}
|
||||
return count.toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate total IPs in a list of CIDR ranges
|
||||
*/
|
||||
export const calculateTotalIPs = (cidrs: string[]): number => {
|
||||
return cidrs.reduce((total, cidr) => total + calculateCIDRSize(cidr), 0);
|
||||
};
|
||||
Reference in New Issue
Block a user