-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathsquid-config.ts
More file actions
186 lines (151 loc) · 6.46 KB
/
squid-config.ts
File metadata and controls
186 lines (151 loc) · 6.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import { SquidConfig } from './types';
import {
parseDomainList,
isDomainMatchedByPattern,
} from './domain-patterns';
/**
* Generates Squid proxy configuration with domain whitelisting
*
* Supports both plain domains and wildcard patterns:
* - Plain domains use dstdomain ACL (efficient, fast matching)
* - Wildcard patterns use dstdom_regex ACL (regex matching)
*
* @example
* // Plain domain: github.com -> acl allowed_domains dstdomain .github.com
* // Wildcard: *.github.com -> acl allowed_domains_regex dstdom_regex -i ^.*\.github\.com$
*/
export function generateSquidConfig(config: SquidConfig): string {
const { domains, port } = config;
// Normalize domains - remove protocol if present
const normalizedDomains = domains.map(domain => {
return domain.replace(/^https?:\/\//, '').replace(/\/$/, '');
});
// Parse domains into plain domains and wildcard patterns
// This also validates all inputs and throws on invalid patterns
const { plainDomains, patterns } = parseDomainList(normalizedDomains);
// Remove redundant plain subdomains (e.g., if github.com is present, api.github.com is redundant)
const uniquePlainDomains = plainDomains.filter((domain, index, arr) => {
// Check if this domain is a subdomain of another plain domain in the list
return !arr.some((otherDomain, otherIndex) => {
if (index === otherIndex) return false;
// Check if domain is a subdomain of otherDomain (but not an exact duplicate)
return domain !== otherDomain && domain.endsWith('.' + otherDomain);
});
});
// Remove plain domains that are already covered by wildcard patterns
const filteredPlainDomains = uniquePlainDomains.filter(domain => {
return !isDomainMatchedByPattern(domain, patterns);
});
// Generate ACL entries for plain domains using dstdomain (fast matching)
const domainAcls = filteredPlainDomains
.map(domain => {
// Add leading dot for subdomain matching unless already present
const domainPattern = domain.startsWith('.') ? domain : `.${domain}`;
return `acl allowed_domains dstdomain ${domainPattern}`;
})
.join('\n');
// Generate ACL entries for wildcard patterns using dstdom_regex
// Use -i flag for case-insensitive matching (DNS is case-insensitive)
const patternAcls = patterns
.map(p => `acl allowed_domains_regex dstdom_regex -i ${p.regex}`)
.join('\n');
// Determine the ACL section and deny rule based on what we have
let aclSection = '';
let denyRule: string;
if (filteredPlainDomains.length > 0 && patterns.length > 0) {
// Both plain domains and patterns
aclSection = `# ACL definitions for allowed domains\n${domainAcls}\n\n# ACL definitions for allowed domain patterns (wildcard)\n${patternAcls}`;
denyRule = 'http_access deny !allowed_domains !allowed_domains_regex';
} else if (filteredPlainDomains.length > 0) {
// Only plain domains
aclSection = `# ACL definitions for allowed domains\n${domainAcls}`;
denyRule = 'http_access deny !allowed_domains';
} else if (patterns.length > 0) {
// Only patterns
aclSection = `# ACL definitions for allowed domain patterns (wildcard)\n${patternAcls}`;
denyRule = 'http_access deny !allowed_domains_regex';
} else {
// No domains - deny all (edge case, should not happen with validation)
aclSection = '# No domains configured';
denyRule = 'http_access deny all';
}
return `# Squid configuration for egress traffic control
# Generated by awf
# Custom log format with detailed connection information
# Format: timestamp client_ip:port dest_domain dest_ip:port protocol method status decision url user_agent
# Note: For CONNECT requests (HTTPS), the domain is in the URL field
logformat firewall_detailed %ts.%03tu %>a:%>p %{Host}>h %<a:%<p %rv %rm %>Hs %Ss:%Sh %ru "%{User-Agent}>h"
# Access log and cache configuration
access_log /var/log/squid/access.log firewall_detailed
cache_log /var/log/squid/cache.log
cache deny all
# Port configuration
http_port ${port}
${aclSection}
# Network ACLs
acl localnet src 10.0.0.0/8
acl localnet src 172.16.0.0/12
acl localnet src 192.168.0.0/16
acl localnet src fc00::/7
acl localnet src fe80::/10
# Port ACLs
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl CONNECT method CONNECT
# Security: Block direct IP address connections (bypass prevention)
# Clients must use domain names, not raw IP addresses
# This prevents bypassing domain-based filtering via direct IP HTTPS connections
acl ip_dst_ipv4 dstdom_regex ^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$
# IPv6: Must contain at least one colon (distinguishes from domain names)
# Matches: ::1, fe80::1, 2001:db8::1, [::1] (bracket notation for URLs)
acl ip_dst_ipv6 dstdom_regex ^\\[?[0-9a-fA-F]*:[0-9a-fA-F:]*\\]?$
# Access rules
# Deny direct IP connections first (before domain filtering)
http_access deny ip_dst_ipv4
http_access deny ip_dst_ipv6
# Deny unsafe ports
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
# Deny requests to unknown domains (not in allow-list)
# This applies to all sources including localnet
${denyRule}
# Allow from trusted sources (after domain filtering)
http_access allow localnet
http_access allow localhost
# Deny everything else
http_access deny all
# Disable caching
cache deny all
# DNS settings
dns_nameservers 8.8.8.8 8.8.4.4
# Forwarded headers
forwarded_for delete
via off
# Error page customization
error_directory /usr/share/squid/errors/en
# Memory and file descriptor limits
cache_mem 64 MB
maximum_object_size 0 KB
# Timeout settings for streaming/long-lived connections (AI inference APIs)
# read_timeout: Time to wait for data from server before giving up
# Increased to accommodate long AI inference calls and SSE streaming
read_timeout 30 minutes
# connect_timeout: Time to wait for TCP connection to origin server
connect_timeout 30 seconds
# request_timeout: Time to wait for client to send first request after connection
request_timeout 2 minutes
# persistent_request_timeout: Time to wait for next request on persistent connection
persistent_request_timeout 2 minutes
# pconn_timeout: How long to keep idle persistent connections to servers
pconn_timeout 2 minutes
# client_lifetime: Maximum time a client connection can be open
# Set high to accommodate long streaming sessions
client_lifetime 8 hours
# half_closed_clients: Allow half-closed connections for streaming
# Critical for SSE where server sends but client doesn't respond
half_closed_clients on
# Debugging (can be enabled for troubleshooting)
# debug_options ALL,1 33,2
`;
}