|
1 | | -import type { PolicyManifest, PolicyRule, SquidConfig } from '../types'; |
| 1 | +import type { PolicyManifest, SquidConfig } from '../types'; |
2 | 2 | import { DEFAULT_DNS_SERVERS } from '../dns-resolver'; |
3 | | -import { parseDomainList } from '../domain-matchers'; |
4 | | -import { formatDomainForSquid, parseDomainConfig } from './domain-acl'; |
| 3 | +import { parseDomainConfig } from './domain-acl'; |
| 4 | +import { |
| 5 | + addApiProxyAllowRules, |
| 6 | + addBlockedDomainRules, |
| 7 | + addBothProtocolAllowRules, |
| 8 | + addDefaultDenyRule, |
| 9 | + addDlpRules, |
| 10 | + addPortSafetyRules, |
| 11 | + addProtocolAllowRules, |
| 12 | + addRawIpBlockRules, |
| 13 | + type PolicyRuleState, |
| 14 | +} from './policy-rules/section-builders'; |
5 | 15 |
|
6 | 16 | /** |
7 | 17 | * Ports that should never be allowed, even with --allow-host-ports |
@@ -49,200 +59,21 @@ export function generatePolicyManifest(config: SquidConfig): PolicyManifest { |
49 | 59 | // Parse, deduplicate, and group domains by protocol (shared logic with generateSquidConfig) |
50 | 60 | const { domainsByProto, patternsByProto } = parseDomainConfig(domains); |
51 | 61 |
|
52 | | - const rules: PolicyRule[] = []; |
53 | | - let order = 0; |
| 62 | + const state: PolicyRuleState = { rules: [], order: 0 }; |
54 | 63 |
|
55 | | - // --- Port safety rules (evaluated first in Squid) --- |
56 | | - rules.push({ |
57 | | - id: 'deny-unsafe-ports', |
58 | | - order: ++order, |
59 | | - action: 'deny', |
60 | | - aclName: '!Safe_ports', |
61 | | - protocol: 'both', |
62 | | - domains: [], |
63 | | - description: 'Deny requests to ports not in Safe_ports ACL (only 80, 443, and user-specified ports allowed)', |
64 | | - }); |
65 | | - rules.push({ |
66 | | - id: 'deny-connect-unsafe-ports', |
67 | | - order: ++order, |
68 | | - action: 'deny', |
69 | | - aclName: 'CONNECT !Safe_ports', |
70 | | - protocol: 'https', |
71 | | - domains: [], |
72 | | - description: 'Deny CONNECT (HTTPS) to ports not in Safe_ports ACL', |
73 | | - }); |
74 | | - |
75 | | - // --- api-proxy allow (before raw-IP deny) --- |
76 | | - if (apiProxyIp) { |
77 | | - rules.push({ |
78 | | - id: 'allow-api-proxy-ip', |
79 | | - order: ++order, |
80 | | - action: 'allow', |
81 | | - aclName: 'allow_api_proxy_ip', |
82 | | - protocol: 'both', |
83 | | - domains: [apiProxyIp], |
84 | | - description: 'Allow connections to the AWF api-proxy sidecar IP before raw-IP deny rules', |
85 | | - }); |
86 | | - rules.push({ |
87 | | - id: 'allow-from-api-proxy', |
88 | | - order: ++order, |
89 | | - action: 'allow', |
90 | | - aclName: 'from_api_proxy', |
91 | | - protocol: 'both', |
92 | | - domains: ['*'], |
93 | | - description: 'Allow unrestricted outbound from api-proxy sidecar (trusted AWF component, not subject to agent domain ACL)', |
94 | | - }); |
95 | | - } |
96 | | - |
97 | | - // --- Raw IP blocking --- |
98 | | - rules.push({ |
99 | | - id: 'deny-raw-ipv4', |
100 | | - order: ++order, |
101 | | - action: 'deny', |
102 | | - aclName: 'dst_ipv4', |
103 | | - protocol: 'both', |
104 | | - domains: ['^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$'], |
105 | | - description: 'Deny requests to raw IPv4 addresses (bypasses domain filtering)', |
106 | | - }); |
107 | | - rules.push({ |
108 | | - id: 'deny-raw-ipv6', |
109 | | - order: ++order, |
110 | | - action: 'deny', |
111 | | - aclName: 'dst_ipv6', |
112 | | - protocol: 'both', |
113 | | - domains: ['^\\[?[0-9a-fA-F:]+\\]?$'], |
114 | | - description: 'Deny requests to raw IPv6 addresses (bypasses domain filtering)', |
115 | | - }); |
116 | | - |
117 | | - // --- DLP rules (if enabled) --- |
118 | | - if (enableDlp) { |
119 | | - rules.push({ |
120 | | - id: 'deny-dlp', |
121 | | - order: ++order, |
122 | | - action: 'deny', |
123 | | - aclName: 'dlp_blocked', |
124 | | - protocol: 'both', |
125 | | - domains: [], |
126 | | - description: 'Deny requests containing credential patterns in URLs (DLP)', |
127 | | - }); |
128 | | - } |
129 | | - |
130 | | - // --- Blocked domains --- |
131 | | - if (blockedDomains && blockedDomains.length > 0) { |
132 | | - const normalizedBlocked = blockedDomains.map(d => d.replace(/^https?:\/\//, '').replace(/\/$/, '')); |
133 | | - const { plainDomains: blockedPlain, patterns: blockedPatterns } = parseDomainList(normalizedBlocked); |
134 | | - |
135 | | - if (blockedPlain.length > 0) { |
136 | | - rules.push({ |
137 | | - id: 'deny-blocked-plain', |
138 | | - order: ++order, |
139 | | - action: 'deny', |
140 | | - aclName: 'blocked_domains', |
141 | | - protocol: 'both', |
142 | | - domains: blockedPlain.map(e => formatDomainForSquid(e.domain)), |
143 | | - description: 'Deny requests to explicitly blocked domains', |
144 | | - }); |
145 | | - } |
146 | | - |
147 | | - if (blockedPatterns.length > 0) { |
148 | | - rules.push({ |
149 | | - id: 'deny-blocked-regex', |
150 | | - order: ++order, |
151 | | - action: 'deny', |
152 | | - aclName: 'blocked_domains_regex', |
153 | | - protocol: 'both', |
154 | | - domains: blockedPatterns.map(p => p.regex), |
155 | | - description: 'Deny requests to explicitly blocked domain patterns', |
156 | | - }); |
157 | | - } |
158 | | - } |
159 | | - |
160 | | - // --- Protocol-specific allow rules --- |
161 | | - if (domainsByProto.http.length > 0) { |
162 | | - rules.push({ |
163 | | - id: 'allow-http-only-plain', |
164 | | - order: ++order, |
165 | | - action: 'allow', |
166 | | - aclName: 'allowed_http_only', |
167 | | - protocol: 'http', |
168 | | - domains: domainsByProto.http.map(d => formatDomainForSquid(d)), |
169 | | - description: 'Allow HTTP-only traffic to these domains (no HTTPS)', |
170 | | - }); |
171 | | - } |
172 | | - if (patternsByProto.http.length > 0) { |
173 | | - rules.push({ |
174 | | - id: 'allow-http-only-regex', |
175 | | - order: ++order, |
176 | | - action: 'allow', |
177 | | - aclName: 'allowed_http_only_regex', |
178 | | - protocol: 'http', |
179 | | - domains: patternsByProto.http.map(p => p.regex), |
180 | | - description: 'Allow HTTP-only traffic matching these patterns', |
181 | | - }); |
182 | | - } |
183 | | - |
184 | | - if (domainsByProto.https.length > 0) { |
185 | | - rules.push({ |
186 | | - id: 'allow-https-only-plain', |
187 | | - order: ++order, |
188 | | - action: 'allow', |
189 | | - aclName: 'allowed_https_only', |
190 | | - protocol: 'https', |
191 | | - domains: domainsByProto.https.map(d => formatDomainForSquid(d)), |
192 | | - description: 'Allow HTTPS-only traffic to these domains (no HTTP)', |
193 | | - }); |
194 | | - } |
195 | | - if (patternsByProto.https.length > 0) { |
196 | | - rules.push({ |
197 | | - id: 'allow-https-only-regex', |
198 | | - order: ++order, |
199 | | - action: 'allow', |
200 | | - aclName: 'allowed_https_only_regex', |
201 | | - protocol: 'https', |
202 | | - domains: patternsByProto.https.map(p => p.regex), |
203 | | - description: 'Allow HTTPS-only traffic matching these patterns', |
204 | | - }); |
205 | | - } |
206 | | - |
207 | | - // --- Both-protocol allow (used in deny rule logic) --- |
208 | | - if (domainsByProto.both.length > 0) { |
209 | | - rules.push({ |
210 | | - id: 'allow-both-plain', |
211 | | - order: ++order, |
212 | | - action: 'allow', |
213 | | - aclName: 'allowed_domains', |
214 | | - protocol: 'both', |
215 | | - domains: domainsByProto.both.map(d => formatDomainForSquid(d)), |
216 | | - description: 'Allow HTTP and HTTPS traffic to these domains', |
217 | | - }); |
218 | | - } |
219 | | - if (patternsByProto.both.length > 0) { |
220 | | - rules.push({ |
221 | | - id: 'allow-both-regex', |
222 | | - order: ++order, |
223 | | - action: 'allow', |
224 | | - aclName: 'allowed_domains_regex', |
225 | | - protocol: 'both', |
226 | | - domains: patternsByProto.both.map(p => p.regex), |
227 | | - description: 'Allow HTTP and HTTPS traffic matching these patterns', |
228 | | - }); |
229 | | - } |
230 | | - |
231 | | - // --- Default deny (final rule) --- |
232 | | - rules.push({ |
233 | | - id: 'deny-default', |
234 | | - order: ++order, |
235 | | - action: 'deny', |
236 | | - aclName: 'all', |
237 | | - protocol: 'both', |
238 | | - domains: [], |
239 | | - description: 'Deny all traffic not matching any allow rule (default deny)', |
240 | | - }); |
| 64 | + addPortSafetyRules(state); |
| 65 | + addApiProxyAllowRules(state, apiProxyIp); |
| 66 | + addRawIpBlockRules(state); |
| 67 | + addDlpRules(state, enableDlp); |
| 68 | + addBlockedDomainRules(state, blockedDomains); |
| 69 | + addProtocolAllowRules(state, domainsByProto, patternsByProto); |
| 70 | + addBothProtocolAllowRules(state, domainsByProto, patternsByProto); |
| 71 | + addDefaultDenyRule(state); |
241 | 72 |
|
242 | 73 | return { |
243 | 74 | version: 1, |
244 | 75 | generatedAt: new Date().toISOString(), |
245 | | - rules, |
| 76 | + rules: state.rules, |
246 | 77 | dangerousPorts: DANGEROUS_PORTS, |
247 | 78 | dnsServers: dnsServers || DEFAULT_DNS_SERVERS, |
248 | 79 | sslBumpEnabled: sslBump ?? false, |
|
0 commit comments