-
Notifications
You must be signed in to change notification settings - Fork 854
Expand file tree
/
Copy pathinit-firewall.sh
More file actions
executable file
·218 lines (182 loc) · 7.38 KB
/
init-firewall.sh
File metadata and controls
executable file
·218 lines (182 loc) · 7.38 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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/bin/bash
# ==========================================================================
# init-firewall.sh — DNS allowlist filter for the dev container
#
# Uses dnsmasq as a local DNS proxy that only resolves allowed domain
# patterns. Unmatched domains get REFUSED.
#
# NOTE: This is a DNS-layer filter, not an IP-layer firewall. Direct-IP
# egress is not blocked. Full iptables egress rules are impractical here
# because the allowed services (NuGet, Azure CDN, GitHub, etc.) resolve
# to large, dynamic IP ranges. The DNS allowlist is sufficient to prevent
# accidental or automated access to unauthorized services.
# ==========================================================================
set -euo pipefail
IFS=$'\n\t'
DNSMASQ_LISTEN="127.0.0.53"
# --------------------------------------------------------------------------
# 1. Upstream DNS for allowed domains
# Priority: $UPSTREAM_DNS env var > first nameserver in /etc/resolv.conf > 8.8.8.8
# --------------------------------------------------------------------------
if [ -z "${UPSTREAM_DNS:-}" ]; then
UPSTREAM_DNS=$(grep -m1 '^nameserver' /etc/resolv.conf | awk '{print $2}' || true)
# Skip loopback (would point back at ourselves after resolv.conf rewrite)
if [ -z "$UPSTREAM_DNS" ] || echo "$UPSTREAM_DNS" | grep -qE '^127\.'; then
UPSTREAM_DNS="8.8.8.8"
fi
fi
echo "Upstream DNS: ${UPSTREAM_DNS}"
# --------------------------------------------------------------------------
# 2. Configure dnsmasq as allowlist DNS proxy
# --------------------------------------------------------------------------
mkdir -p /etc/dnsmasq.d
cat > /etc/dnsmasq.d/allowlist.conf << EOF
# Listen on local address only
listen-address=${DNSMASQ_LISTEN}
bind-interfaces
port=53
# Don't read /etc/resolv.conf or poll for changes
no-resolv
no-poll
# =====================================================
# ALLOWED DOMAIN PATTERNS
# Each server= line forwards that domain + all subdomains
# to the upstream DNS. Unmatched domains get REFUSED.
# =====================================================
# GitHub (git, gh CLI, PR workflows, raw content)
server=/github.com/${UPSTREAM_DNS}
server=/githubusercontent.com/${UPSTREAM_DNS}
# GitHub Copilot CLI / editor endpoints
server=/githubcopilot.com/${UPSTREAM_DNS}
server=/api.business.githubcopilot.com/${UPSTREAM_DNS}
server=/exp-tas.com/${UPSTREAM_DNS}
server=/ghe.com/${UPSTREAM_DNS}
# Certificate revocation/OCSP endpoints used by Copilot TLS chains
server=/digicert.com/${UPSTREAM_DNS}
server=/symantec.com/${UPSTREAM_DNS}
server=/symcb.com/${UPSTREAM_DNS}
server=/symcd.com/${UPSTREAM_DNS}
server=/geotrust.com/${UPSTREAM_DNS}
server=/thawte.com/${UPSTREAM_DNS}
server=/verisign.com/${UPSTREAM_DNS}
server=/globalsign.com/${UPSTREAM_DNS}
server=/ssl.com/${UPSTREAM_DNS}
server=/identrust.com/${UPSTREAM_DNS}
server=/sectigo.com/${UPSTREAM_DNS}
server=/usertrust.com/${UPSTREAM_DNS}
# npm (Claude Code install/updates)
server=/npmjs.org/${UPSTREAM_DNS}
server=/npmjs.com/${UPSTREAM_DNS}
# Anthropic (API, feature flags)
server=/anthropic.com/${UPSTREAM_DNS}
# Error reporting & feature flags
server=/sentry.io/${UPSTREAM_DNS}
server=/statsig.com/${UPSTREAM_DNS}
# VS Code (marketplace, extensions, updates)
server=/visualstudio.com/${UPSTREAM_DNS}
# Azure Blob Storage — covers ALL *.blob.core.windows.net
server=/core.windows.net/${UPSTREAM_DNS}
# NuGet
server=/nuget.org/${UPSTREAM_DNS}
# Anthropic
server=/claude.com/${UPSTREAM_DNS}
# Azure DevOps (Uno Features feed)
server=/dev.azure.com/${UPSTREAM_DNS}
# Azure CDN (.NET workloads, SDK downloads)
server=/azureedge.net/${UPSTREAM_DNS}
# Azure AD auth (required for DevOps feed auth)
server=/microsoftonline.com/${UPSTREAM_DNS}
# .NET SDK & workloads
server=/dotnet.microsoft.com/${UPSTREAM_DNS}
# Microsoft Learn (MCP documentation server)
server=/learn.microsoft.com/${UPSTREAM_DNS}
# Uno domains
server=/platform.uno/${UPSTREAM_DNS}
server=/unoplatform.net/${UPSTREAM_DNS}
# Figma MCP
server=/figma.com/${UPSTREAM_DNS}
# Playwright
server=/cdn.playwright.dev/${UPSTREAM_DNS}
server=/storage.googleapis.com/${UPSTREAM_DNS}
EOF
# --------------------------------------------------------------------------
# 3. Start dnsmasq
# --------------------------------------------------------------------------
echo "Starting dnsmasq DNS proxy..."
dnsmasq --conf-file=/etc/dnsmasq.d/allowlist.conf --test 2>&1 && echo " dnsmasq config OK"
pkill dnsmasq 2>/dev/null || true
sleep 0.2
dnsmasq --conf-file=/etc/dnsmasq.d/allowlist.conf
sleep 0.2
if pgrep dnsmasq >/dev/null; then
echo " dnsmasq running on ${DNSMASQ_LISTEN} (pid $(pgrep dnsmasq))"
else
echo "ERROR: dnsmasq failed to start"
exit 1
fi
# --------------------------------------------------------------------------
# 4. Point resolver at dnsmasq
# --------------------------------------------------------------------------
# Docker bind-mounts resolv.conf; back it up, then replace nameserver lines
# while preserving search/options/sortlist directives.
cp /etc/resolv.conf /etc/resolv.conf.docker.bak
umount /etc/resolv.conf 2>/dev/null || true
{ grep -v '^nameserver' /etc/resolv.conf.docker.bak || true; echo "nameserver ${DNSMASQ_LISTEN}"; } > /etc/resolv.conf
# Verify resolv.conf was actually changed — fail hard if not
if grep -q "${DNSMASQ_LISTEN}" /etc/resolv.conf; then
echo " DNS resolver pointed to dnsmasq"
else
echo "ERROR: resolv.conf was not updated — DNS filtering is NOT active"
echo " Contents:"
cat /etc/resolv.conf
exit 1
fi
echo ""
echo "========================================="
echo " DNS allowlist filter configured"
echo "========================================="
# --------------------------------------------------------------------------
# 5. Verification
# --------------------------------------------------------------------------
echo ""
echo "Verifying..."
# Should be BLOCKED (DNS refuses resolution)
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed — was able to reach https://example.com"
exit 1
else
echo " PASS: https://example.com is blocked (DNS refused)"
fi
# Should be ALLOWED
if ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed — unable to reach https://api.github.com"
exit 1
else
echo " PASS: https://api.github.com is reachable"
fi
if ! curl --connect-timeout 5 https://api.nuget.org/v3/index.json >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed — unable to reach https://api.nuget.org"
exit 1
else
echo " PASS: https://api.nuget.org is reachable"
fi
if ! curl --connect-timeout 5 https://api.githubcopilot.com/_ping >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed — unable to reach https://api.githubcopilot.com"
exit 1
else
echo " PASS: https://api.githubcopilot.com is reachable"
fi
if ! curl --connect-timeout 5 https://copilot-proxy.githubusercontent.com/_ping >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed — unable to reach https://copilot-proxy.githubusercontent.com"
exit 1
else
echo " PASS: https://copilot-proxy.githubusercontent.com is reachable"
fi
if ! curl --connect-timeout 5 https://default.exp-tas.com >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed — unable to reach https://default.exp-tas.com"
exit 1
else
echo " PASS: https://default.exp-tas.com is reachable"
fi
echo ""
echo "Firewall verification passed"