↓ Ir para o conteúdo principal
  1. Posts/

Isole a VM antes de expor seu homelab à internet (Parte 1)

Autor
Gustavo Novello
Apaixonado por virtualização, Linux e tecnologias de código aberto.
Expondo serviços do homelab sem abrir portas - Este artigo faz parte de uma série de artigos.
Parte 1: Este artigo

Este blog roda numa VM dentro do meu homelab. Ninguém na internet consegue abrir uma conexão com a minha casa, e a VM, por sua vez, não enxerga nada da minha rede além do roteador. Esta série mostra como montar isso do zero, para qualquer serviço HTTP: um blog, um Nextcloud, um painel, uma API.

A peça mais conhecida é o Cloudflare Tunnel, que publica o serviço sem abrir portas no roteador e funciona até atrás de CGNAT. Mas ele só cuida do caminho de entrada. Se o serviço for invadido, o atacante continua na mesma rede que o seu NAS, as câmeras e o seu notebook. Por isso a série começa pelo isolamento, antes de expor qualquer coisa.

A série tem três partes:

  1. Isolamento (este post): a VM exposta não alcança o resto da casa.
  2. Túnel: publicar o serviço com Cloudflare Tunnel.
  3. Pipeline: deploy automático com GitHub Actions e um runner dentro da VM isolada.

Você não precisa de equipamento especial para acompanhar. Uma VM Linux no hypervisor que você já usa basta, e se o seu roteador suporta VLAN, melhor ainda.

O diagrama abaixo mostra o sistema inteiro, do git push até o visitante. O trecho destacado é o deste post.

Diagrama de como o blog é publicado e servido, com a parte de rede (firewall e rede isolada) destacada
No meu lab, a barreira é o firewall do roteador, com a VM numa VLAN própria (a opção 1 deste post).

O que significa isolar uma VM exposta
#

Antes de escolher a ferramenta, vale definir o comportamento esperado. A VM que vai para a internet deve:

  • não abrir conexão com nenhum outro dispositivo da rede (NAS, roteador, outras VMs);
  • não aceitar conexão de ninguém, a não ser da máquina de onde você a administra;
  • conseguir sair para a internet, porque o túnel da Parte 2 é uma conexão que a própria VM abre para fora;
  • usar o DNS do roteador.
flowchart LR
  internet((Internet))
  subgraph casa[Sua rede]
    direction LR
    nas[NAS, notebook,
roteador, outras VMs] admin[Sua máquina
de administração] vm[VM exposta] end vm -- "saída: liberada" --> internet vm -. "bloqueado pelo firewall" .-> nas admin -- "SSH: liberado" --> vm internet -. "nenhuma porta aberta" .-> vm

Firewall no roteador ou ufw na VM?
#

Há dois lugares para colocar essa barreira, e a diferença entre eles aparece justamente no pior cenário.

Firewall no roteador (com VLAN)ufw na própria VM
Onde a regra ficano roteador, fora da VMdentro da VM
Precisa deroteador com VLAN e firewall (UniFi, OPNsense, pfSense, OpenWrt) e um switch gerenciávelnada além da VM
Se a VM for invadidaa regra continua valendoquem virar root pode desligar o ufw
Containers Dockertambém ficam isoladosescapam das regras de saída (veja abaixo)

Se o seu roteador permite, fique com a primeira opção. Se não permite, o ufw ainda é muito melhor do que deixar a VM solta na rede.

Opção 1: VLAN com firewall no roteador (o que eu uso)
#

Aqui a VM sai da rede de casa e ganha uma rede só dela. Quem decide o que entra e o que sai é o roteador.

No meu caso, o roteador é um gateway UniFi (Network 10.6) com firewall por zonas, mas o mesmo desenho funciona em OPNsense, pfSense ou OpenWrt. Muda só a tela onde você clica.

Criei uma rede na VLAN 101 e coloquei essa rede na zona DMZ, que já vem pronta no UniFi. Não precisei escrever nenhuma regra de bloqueio, porque as políticas padrão da zona já fazem o isolamento inteiro. Esta é a matriz que o meu gateway aplica hoje, conferida direto na API dele:

De → ParaResultado
DMZ → rede interna, lab, monitoração, VPN, visitantesbloqueado
DMZ → DMZ (entre redes da zona)bloqueado
DMZ → gatewaybloqueado, exceto DNS, DHCP e ICMP
DMZ → internetliberado
internet → DMZbloqueado (só passa o tráfego de retorno)
rede interna → DMZliberado (é por onde eu administro)

Em outro roteador, o equivalente é um conjunto de regras na interface da VLAN, nesta ordem: liberar DNS para o gateway, bloquear todas as redes privadas e liberar o resto.

Do lado do hypervisor, a VM precisa estar na VLAN. No Proxmox, isso pede uma bridge que entenda VLANs, ligada a uma porta do switch que transporte a VLAN 101 com tag. A minha /etc/network/interfaces está assim:

auto vmbr0
iface vmbr0 inet static
	bridge-ports bond0
	bridge-stp off
	bridge-fd 0
	bridge-vlan-aware yes
	bridge-vids 2-4094

Na VM, basta definir a tag. Como eu crio as VMs a partir de um template com cloud-init, o IP vem junto:

qm set 101 --net0 virtio=BC:24:11:AA:BB:CC,bridge=vmbr0,tag=101
qm set 101 --ipconfig0 ip=10.10.101.10/24,gw=10.10.101.1
qm set 101 --nameserver 10.10.101.1

Em outros hypervisors, procure o campo de VLAN ID na placa de rede da VM.

No meu lab, o isolamento para por aí. A VM não tem ufw ativo, e tudo vem da rede.

Opção 2: ufw na própria VM (quando o roteador não ajuda)
#

Se a sua rede é a do roteador da operadora, sem VLAN, quem precisa se proteger é a própria VM. Nos exemplos abaixo, a rede de casa é 192.168.1.0/24, o roteador é 192.168.1.1, a sua máquina de administração é 192.168.1.50 e a VM exposta é 192.168.1.200. Troque pelos seus endereços.

Na VM (Ubuntu ou Debian):

sudo ufw default deny incoming
sudo ufw default allow outgoing

# entrada: só SSH, e só da sua máquina
sudo ufw allow from 192.168.1.50 to any port 22 proto tcp

# saída: DNS pelo roteador primeiro, depois bloqueia as redes privadas
sudo ufw allow out to 192.168.1.1 port 53
sudo ufw deny out to 10.0.0.0/8
sudo ufw deny out to 192.168.0.0/16

sudo ufw enable
sudo ufw status numbered

O que cada parte faz:

  • Nada entra, exceto o SSH vindo da sua máquina. Essa regra vem antes do enable para você não derrubar a própria sessão.
  • O ufw avalia as regras em ordem. O DNS para o roteador passa primeiro, e depois disso qualquer destino em 10.0.0.0/8 ou 192.168.0.0/16 é bloqueado, incluindo a interface do roteador, o NAS e as outras VMs.
  • O resto, ou seja, a internet, fica liberado. Respostas de conexões já estabelecidas também passam, então o SSH funciona normalmente.
  • Se a VM pega IP por DHCP, adicione sudo ufw allow out to 192.168.1.1 port 67 proto udp antes dos deny.

Talvez você tenha estranhado a falta de 172.16.0.0/12 na lista. Essa é a faixa que o Docker usa para as redes dos containers, e bloqueá-la faz o host perder o acesso aos próprios containers. Se a sua rede de casa estiver nessa faixa, libere antes as redes do Docker (docker network inspect mostra quais são) e só depois bloqueie.

Docker ignora o ufw (e outros limites)
#

O ufw tem quatro limites que vale conhecer antes de confiar nele.

  • Quem virar root na VM desliga o ufw com um comando. A barreira segura erros de configuração e um invasor sem privilégios, mas não um invasor com root.
  • As regras de saída não valem para containers Docker. O tráfego deles passa pela chain FORWARD, que o Docker controla antes do ufw, então um container comprometido alcança a sua rede mesmo com o ufw ativo. Para filtrar containers, use a chain DOCKER-USER (documentação do Docker sobre firewall).
  • Portas publicadas pelo Docker também passam por cima do ufw na entrada. Publique os serviços só em 127.0.0.1 (-p 127.0.0.1:8080:8080), já que o túnel da Parte 2 só precisa alcançar localhost.
  • A VM continua na mesma rede (camada 2) que o resto da casa e vê o tráfego de broadcast dela.

Como testar o isolamento
#

Qualquer que seja a opção, teste de dentro da VM antes de confiar na configuração. Os comandos abaixo usam os endereços da opção 2; na opção 1, troque o roteador pelo gateway da VLAN.

# deve funcionar: DNS pelo roteador e saída para a internet
getent hosts github.com
curl -sI https://github.com | head -1

# deve FALHAR (timeout): outro dispositivo da rede, como o NAS
nc -vz -w 3 192.168.1.10 445

# deve FALHAR: a interface web do roteador
curl -m 5 -sI http://192.168.1.1 | head -1

Se o nc ou o último curl responderem, alguma regra está liberando esse tráfego ou a ordem das regras está errada. No ufw, sudo ufw status numbered mostra a ordem.

Quem usa Docker deve repetir o último teste de dentro de um container: docker run --rm curlimages/curl -m 5 -sI http://192.168.1.1. Na opção 2, é provável que ele responda, pelo motivo explicado acima.

O que este isolamento não resolve
#

Nenhuma das duas opções resolve tudo. Três pontos continuam em aberto.

  • A saída para a internet está toda liberada, então um serviço comprometido pode baixar ferramentas ou mandar dados para fora. Dá para apertar isso. O túnel da Parte 2 precisa de saída para a Cloudflare nas portas 7844 (TCP e UDP) e 443, e a VM precisa de 80 e 443 para atualizações e de 123/UDP para NTP. Liberar só isso fecha bastante o cerco; o preço é lembrar dessa lista toda vez que instalar algo novo.
  • O hypervisor é compartilhado. Isolamento de rede não protege contra uma falha no próprio hypervisor, e agentes como o qemu-guest-agent abrem um canal de administração que não passa pela rede. Quem controla o host controla a VM, com ou sem firewall.
  • A monitoração também fica do lado de fora. Se você usa Zabbix, Prometheus ou algo parecido, o servidor de monitoração também é bloqueado. Libere só a porta do agente, de forma explícita.

Próximo passo: o túnel
#

Com isso, temos uma VM que não fala com nada da sua rede e que ninguém na internet alcança. Na Parte 2, o serviço dela vai para o ar com um Cloudflare Tunnel: a VM abre a conexão para fora, e os visitantes chegam por ela, sem nenhuma porta aberta no roteador.

Se você isolou o seu serviço de outro jeito, com OPNsense, OpenWrt ou outra ferramenta, conta nos comentários.

Referências:

Expondo serviços do homelab sem abrir portas - Este artigo faz parte de uma série de artigos.
Parte 1: Este artigo

Relacionados