Skip to content

Commit 7cb59e2

Browse files
committed
feat: agregar módulo de egreso DNS para subredes Docker y actualizar documentación
1 parent 71ee602 commit 7cb59e2

File tree

7 files changed

+184
-15
lines changed

7 files changed

+184
-15
lines changed

docs/iptables.rules.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ modules/
5050
├─ ip_05_loopback.sh
5151
├─ ip_10_whitelist.sh
5252
├─ ip_20_allowlist_ports.sh
53-
├─ ip_30_openvpn.sh
53+
├─ ip_22_docker_dns_egress.sh
54+
├─ ip_30_openvpn_server.sh
55+
├─ ip_31_openvpn_sitetosite.sh
5456
├─ ip_35_tcpfilter_chain.sh
5557
├─ ip_40_tcp_ssh.sh
5658
├─ ip_45_http_https_protect.sh
@@ -108,6 +110,9 @@ SSH_PORT="22" # Puertos SSH del sistema
108110
WHITELISTED_IPS="" # IPs con acceso completo al sistema (⚠️ TODOS los puertos)
109111
WHITELISTED_DOMAINS="" # Dominios resueltos a IPv4 y fusionados con WHITELISTED_IPS
110112

113+
# DNS saliente para contenedores Docker (ACME / resolución interna)
114+
DOCKER_DNS_EGRESS_SUBNETS="172.16.0.0/12"
115+
111116
# === LOGGING DETALLADO ===
112117
LOG_PREFIX_INVALID_SIZE="INVALID_SIZE: "
113118
LOG_PREFIX_MALFORMED="MALFORMED: "
@@ -223,6 +228,9 @@ sudo ./iptables.rules.sh
223228
# Ejecutar subconjunto de módulos
224229
sudo ./iptables.rules.sh --only ip_whitelist --only ip_openvpn_server
225230
231+
# Ejecutar módulo DNS egress Docker
232+
sudo ./iptables.rules.sh --only ip_docker_dns_egress
233+
226234
# Omitir módulos específicos
227235
sudo ./iptables.rules.sh --skip ip_openvpn_server
228236

docs/modular-loader-architecture.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ Ambos deben cargar módulos dinámicamente desde modules sin conocer de antemano
4242
│ │ ├─ ip_05_loopback.sh
4343
│ │ ├─ ip_10_whitelist.sh
4444
│ │ ├─ ip_20_allowlist_ports.sh
45-
│ │ ├─ ip_30_openvpn.sh
45+
│ │ ├─ ip_22_docker_dns_egress.sh
46+
│ │ ├─ ip_30_openvpn_server.sh
47+
│ │ ├─ ip_31_openvpn_sitetosite.sh
4648
│ │ ├─ ip_35_tcpfilter_chain.sh
4749
│ │ ├─ ip_40_tcp_ssh.sh
4850
│ │ ├─ ip_42_l4d2_tcp_protect.sh
@@ -54,7 +56,9 @@ Ambos deben cargar módulos dinámicamente desde modules sin conocer de antemano
5456
│ ├─ nf_35_l4d2_tcpfilter_chain.sh
5557
│ ├─ nf_10_whitelist.sh
5658
│ ├─ nf_20_allowlist_ports.sh
57-
│ ├─ nf_30_openvpn.sh
59+
│ ├─ nf_22_docker_dns_egress.sh
60+
│ ├─ nf_30_openvpn_server.sh
61+
│ ├─ nf_31_openvpn_sitetosite.sh
5862
│ ├─ nf_40_tcp_ssh.sh
5963
│ ├─ nf_42_l4d2_tcp_protect.sh
6064
│ ├─ nf_45_http_https_protect.sh
@@ -77,17 +81,17 @@ Notas:
7781

7882
Cada módulo debe implementar funciones con namespace del módulo para evitar colisiones.
7983

80-
Ejemplo para modules/ip/ip_30_openvpn.sh:
84+
Ejemplo para modules/ip/ip_30_openvpn_server.sh:
8185

82-
- ip_30_openvpn_metadata
83-
- ip_30_openvpn_validate
84-
- ip_30_openvpn_apply
86+
- ip_30_openvpn_server_metadata
87+
- ip_30_openvpn_server_validate
88+
- ip_30_openvpn_server_apply
8589

86-
Ejemplo para modules/nf/nf_30_openvpn.sh:
90+
Ejemplo para modules/nf/nf_30_openvpn_server.sh:
8791

88-
- nf_30_openvpn_metadata
89-
- nf_30_openvpn_validate
90-
- nf_30_openvpn_apply
92+
- nf_30_openvpn_server_metadata
93+
- nf_30_openvpn_server_validate
94+
- nf_30_openvpn_server_apply
9195

9296
### Formato de metadata (salida KEY=VALUE)
9397

@@ -236,10 +240,10 @@ Recomendaciones:
236240

237241
1. iptables.rules.sh invoca preload.
238242
2. Descubre `ip_*.sh` en `modules/ip` + `modules/`.
239-
3. Carga ip_30_openvpn.sh.
243+
3. Carga ip_30_openvpn_server.sh.
240244
4. Lee metadata y resuelve VPN_PORT por CLI/.env/default.
241-
5. Ejecuta ip_30_openvpn_validate.
242-
6. Ejecuta ip_30_openvpn_apply.
245+
5. Ejecuta ip_30_openvpn_server_validate.
246+
6. Ejecuta ip_30_openvpn_server_apply.
243247
7. Repite con el resto de módulos.
244248
8. Ejecuta postload con resumen final.
245249

@@ -286,7 +290,9 @@ sudo ./nftables.rules.sh --set TYPECHAIN=2 --set VPN_PORT=1194
286290
| Loopback | `ip_05_loopback` | Integrado en `nf_chain_setup` | ✅ Equivalente funcional |
287291
| Whitelist IP | `ip_10_whitelist` | `nf_10_whitelist` | ✅ Implementado |
288292
| Allowlist de puertos | `ip_20_allowlist_ports` | `nf_20_allowlist_ports` | ✅ Implementado |
289-
| OpenVPN base | `ip_30_openvpn` | `nf_30_openvpn` | ✅ Implementado |
293+
| DNS egress Docker | `ip_22_docker_dns_egress` | `nf_22_docker_dns_egress` | ✅ Implementado |
294+
| OpenVPN server | `ip_30_openvpn_server` | `nf_30_openvpn_server` | ✅ Implementado |
295+
| OpenVPN site-to-site | `ip_31_openvpn_sitetosite` | `nf_31_openvpn_sitetosite` | ✅ Implementado |
290296
| Cadena TCP anti-spam | `ip_l4d2_tcpfilter_chain` | `nf_l4d2_tcpfilter_chain` (compat/no-op) | ✅ Implementado |
291297
| SSH base | `ip_40_tcp_ssh` | `nf_40_tcp_ssh` | ✅ Implementado |
292298
| Protección TCP L4D2 | `ip_l4d2_tcp_protect` | `nf_l4d2_tcp_protect` | ✅ Implementado |
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Módulo unificado — docker_dns_egress (ip/nf)
2+
3+
## Objetivo
4+
Permitir salida DNS (`UDP/TCP 53`) desde subredes de contenedores Docker para evitar bloqueos en resolución (por ejemplo, ACME/Cloudflare en Traefik).
5+
6+
## Backends
7+
- iptables: `modules/ip/ip_22_docker_dns_egress.sh` (`ID=ip_docker_dns_egress`)
8+
- nftables: `modules/nf/nf_22_docker_dns_egress.sh` (`ID=nf_docker_dns_egress`)
9+
10+
## Alias de activación
11+
- `docker_dns_egress`
12+
13+
## Variables
14+
- `TYPECHAIN`
15+
- `DOCKER_DNS_EGRESS_SUBNETS` (CIDRs separados por coma)
16+
17+
## Defaults
18+
- `DOCKER_DNS_EGRESS_SUBNETS="172.16.0.0/12"`
19+
20+
## Alcance de reglas
21+
- `nftables`: agrega reglas en cadena `forward`.
22+
- `iptables`: agrega reglas en cadena `DOCKER-USER` (cuando `TYPECHAIN` incluye flujo Docker).
23+
24+
## Nota operativa
25+
Este módulo abre únicamente DNS saliente para subredes Docker definidas; no expone servicios DNS del host a clientes externos.

docs/modules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ Breve índice de la documentación de cada módulo. Cada entrada enlaza al fiche
1717
- [13_ip_loopback.md](13_ip_loopback.md) — Módulo específico `ip`.
1818
- [14_ip_l4d2_tcpfilter_chain.md](14_ip_l4d2_tcpfilter_chain.md) — Módulo específico `ip` (`ip_l4d2_tcpfilter_chain`).
1919
- [15_l4d2_tcp_protect.md](15_l4d2_tcp_protect.md) — Protección TCP L4D2 (ip/nf).
20+
- [16_docker_dns_egress.md](16_docker_dns_egress.md) — Egreso DNS para subredes Docker (ip/nf).

example.env

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ OVPNS2S_LOG_PREFIX="VPN_S2S_TRAFFIC: "
101101
UDP_ALLOW_PORTS=""
102102
TCP_ALLOW_PORTS=""
103103

104+
# Docker DNS egress subnet allowlist (for ACME and container name resolution)
105+
# Allows UDP/TCP 53 from Docker bridge subnets through FORWARD/DOCKER-USER.
106+
# Default Docker bridge range is typically 172.16.0.0/12.
107+
# Comma-separated CIDRs.
108+
DOCKER_DNS_EGRESS_SUBNETS="172.16.0.0/12"
109+
104110
# Source Engine game server ports (GameServer)
105111
# These ports handle: player connections, A2S queries, Steam communication
106112
# Standard format: "27001:27016" (16 servers), "27015" (single server)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/bin/bash
2+
3+
ip_22_docker_dns_egress_add_first() {
4+
local chain="$1"
5+
shift
6+
iptables -C "$chain" "$@" 2>/dev/null || iptables -I "$chain" 1 "$@"
7+
}
8+
9+
ip_22_docker_dns_egress_metadata() {
10+
cat << 'EOF'
11+
ID=ip_docker_dns_egress
12+
ALIASES=docker_dns_egress
13+
DESCRIPTION=Allows DNS egress (UDP/TCP 53) from Docker bridge subnets in DOCKER-USER
14+
REQUIRED_VARS=TYPECHAIN
15+
OPTIONAL_VARS=DOCKER_DNS_EGRESS_SUBNETS
16+
DEFAULTS=TYPECHAIN=0 DOCKER_DNS_EGRESS_SUBNETS=172.16.0.0/12
17+
EOF
18+
}
19+
20+
ip_22_docker_dns_egress_validate() {
21+
case "${TYPECHAIN:-}" in
22+
0|1|2) ;;
23+
*)
24+
echo "ERROR: ip_docker_dns_egress: TYPECHAIN must be 0, 1 or 2"
25+
return 2
26+
;;
27+
esac
28+
}
29+
30+
ip_22_docker_dns_egress_apply() {
31+
if [ "$TYPECHAIN" -eq 0 ]; then
32+
return 0
33+
fi
34+
35+
iptables -N DOCKER-USER 2>/dev/null || true
36+
37+
local item trimmed
38+
IFS=',' read -r -a _subnets <<< "${DOCKER_DNS_EGRESS_SUBNETS:-}"
39+
for item in "${_subnets[@]}"; do
40+
trimmed="$item"
41+
trimmed="${trimmed#"${trimmed%%[![:space:]]*}"}"
42+
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
43+
[ -z "$trimmed" ] && continue
44+
45+
ip_22_docker_dns_egress_add_first DOCKER-USER -s "$trimmed" -p udp --dport 53 -j ACCEPT
46+
ip_22_docker_dns_egress_add_first DOCKER-USER -s "$trimmed" -p tcp --dport 53 -j ACCEPT
47+
done
48+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/bin/bash
2+
3+
# shellcheck disable=SC1090
4+
. "${PROJECT_ROOT:-.}/modules/common_nft.sh"
5+
6+
nf_22_docker_dns_egress_metadata() {
7+
cat << 'EOF'
8+
ID=nf_docker_dns_egress
9+
ALIASES=docker_dns_egress
10+
DESCRIPTION=Allows DNS egress (UDP/TCP 53) from Docker bridge subnets on FORWARD chain
11+
REQUIRED_VARS=TYPECHAIN
12+
OPTIONAL_VARS=DOCKER_DNS_EGRESS_SUBNETS
13+
DEFAULTS=TYPECHAIN=0 DOCKER_DNS_EGRESS_SUBNETS=172.16.0.0/12
14+
EOF
15+
}
16+
17+
nf_22_docker_dns_egress_is_ipv4_cidr() {
18+
local cidr="$1"
19+
local ip prefix a b c d octet
20+
21+
[[ "$cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]] || return 1
22+
23+
ip="${cidr%/*}"
24+
prefix="${cidr#*/}"
25+
26+
IFS='.' read -r a b c d <<< "$ip"
27+
for octet in "$a" "$b" "$c" "$d"; do
28+
[ "$octet" -ge 0 ] && [ "$octet" -le 255 ] || return 1
29+
done
30+
31+
[ "$prefix" -ge 0 ] && [ "$prefix" -le 32 ]
32+
}
33+
34+
nf_22_docker_dns_egress_validate() {
35+
case "${TYPECHAIN:-}" in
36+
0|1|2) ;;
37+
*)
38+
echo "ERROR: nf_docker_dns_egress: TYPECHAIN must be 0, 1 or 2"
39+
return 2
40+
;;
41+
esac
42+
43+
local raw="${DOCKER_DNS_EGRESS_SUBNETS:-}"
44+
local item trimmed
45+
46+
IFS=',' read -r -a _subnets <<< "$raw"
47+
for item in "${_subnets[@]}"; do
48+
trimmed="$item"
49+
trimmed="${trimmed#"${trimmed%%[![:space:]]*}"}"
50+
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
51+
[ -z "$trimmed" ] && continue
52+
53+
nf_22_docker_dns_egress_is_ipv4_cidr "$trimmed" || {
54+
echo "ERROR: nf_docker_dns_egress: invalid CIDR '$trimmed' in DOCKER_DNS_EGRESS_SUBNETS"
55+
return 2
56+
}
57+
done
58+
}
59+
60+
nf_22_docker_dns_egress_apply() {
61+
nf_chain_enabled forward || return 0
62+
63+
local item trimmed
64+
IFS=',' read -r -a _subnets <<< "${DOCKER_DNS_EGRESS_SUBNETS:-}"
65+
66+
for item in "${_subnets[@]}"; do
67+
trimmed="$item"
68+
trimmed="${trimmed#"${trimmed%%[![:space:]]*}"}"
69+
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
70+
[ -z "$trimmed" ] && continue
71+
72+
nf_add_rule forward ip saddr "$trimmed" udp dport 53 accept
73+
nf_add_rule forward ip saddr "$trimmed" tcp dport 53 accept
74+
done
75+
}

0 commit comments

Comments
 (0)