Linux

Roteador Linux - Parte 4 - Podman e Unbound

Esta é a quarta parte de uma série multipartes que descreve como construir seu próprio roteador Linux.

Nos artigos anteriores, instalamos o sistema operacional, configuramos o Mac Mini como Gateway de internet usando PPPoE e realizamos ajustes de segurança configurando os métodos de autenticação e o firewall.

Agora, instalaremos o Podman, um substituto direto para o Docker com alguns recursos interessantes, e configuraremos o Unbound para rodar nele.

Foca em frente a uma corda
Imagem gerada por IA do Gemini do Google

Índice

Sobre o Podman

Como o NixOS é configurado usando arquivos .nix, pode parecer simples instalar os serviços necessários diretamente, sem a necessidade de containerização. Em muitos casos, essa abordagem faz sentido, já que a sobrecarga e a complexidade da containerização podem não ser justificadas. No entanto, considerando o vasto número de imagens Docker pré-configuradas disponíveis que atendem às nossas necessidades, não vejo motivos para não aproveitar dessas imagens usando o Podman.

Por que Podman em vez de Docker?

Existem várias vantagens em usar o Podman em vez do Docker. Embora este tópico mereça um artigo próprio, aqui estão alguns pontos relevantes:

  1. Arquitetura sem Daemon: O Podman não requer um daemon central para executar containers. Cada container é executado como um processo filho do comando Podman, melhorando a segurança e reduzindo o risco de um ponto único de falha.
  2. Containers sem Root: O Podman permite que containers sejam executados sem exigir privilégios de root, aumentando a segurança.
  3. Compatibilidade com Kubernetes: O Podman pode gerar arquivos YAML do Kubernetes diretamente a partir de containers ou pods em execução, facilitando a transição do desenvolvimento local para ambientes Kubernetes.
  4. CLI Compatível com Docker: A maioria dos comandos Docker pode ser usada com o Podman sem modificação, tornando a transição do Docker para o Podman tranquila.

Sobre o Unbound?

O Unbound é um servidor DNS que armazena em cache consultas DNS em um repositório local, otimizando a resolução de DNS, reduzindo o tráfego e aumentando ligeiramente a velocidade da internet. Além disso, com alguns scripts, o Unbound pode operar como um bloqueador de anúncios através do bloqueio desses hosts.

Para este projeto, usarei uma imagem Docker do Unbound que criei há algum tempo: cjuniorfox/unbound. Nesta há três funcionalidades relevantes:

  • Resolução de nomes DNS.
  • Bloqueio de anúncios aplicando a lista StevenBlack/hosts diariamente.
  • Resolução de nomes para a rede local, recuperando nomes de host do Servidor DHCP e atribuindo-os aos endereços do nameserver do Unbound.

Configuração do Podman

Vamos começar instalando o Podman em nosso sistema NixOS.

1. Atualizar o arquivo de configuração do NixOS

Nota: Atualize apenas as partes relevantes do arquivo. Não substitua o arquivo inteiro pelo conteúdo abaixo.

Edite o arquivo /etc/nixos/configuration.nix:

{ config, pkgs, ... }:
{
  ...
  boot = {
    kernelParams = [ "systemd.unified_cgroup_hierarchy=1" ];
    ...
  };
  ...
  imports = [
    ...
    ./modules/podman.nix
  ]
}

Crie o arquivo modules/podman.nix

/etc/nixos/modules/podman.nix

{ pkgs, config, ... }:
{
  virtualisation = {
    containers.enable = true;
    containers.storage.settings.storage.driver="zfs";
    podman = {
      enable = true;
      defaultNetwork.settings.dns_enabled = true;
    };
  };
  environment.systemPackages = with pkgs; [
    dive # para inspecionar camadas de imagens docker
    podman-tui # status dos containers no terminal
  ];
  systemd.services.podman-autostart = {
    enable = true;
    after = [ "podman.service" ];
    wantedBy = [ "multi-user.target" ];
    description = "Automatically start containers with --restart=always tag";
    serviceConfig = {
      Type = "idle";
      Environment=''LOGGING="--log-level=info"'';
      ExecStartPre = ''${pkgs.coreutils}/bin/sleep 1'';
      ExecStart = ''/run/current-system/sw/bin/podman $LOGGING start --all --filter restart-policy=always'';
      ExecStop="/bin/sh -c '/run/current-system/sw/bin/podman $LOGGING stop $(/run/current-system/bin/podman container ls --filter restart-policy=always -q)'";
      # User = "<user-name>"; Usuário apenas em caso de rootless https://discourse.nixos.org/t/rootless-podman-compose-configuration/52523/4
    };
  };
}

Vamos aplicar essas mudanças para ter o Podman instalado e funcionando.

nixos-rebuild switch

2. Crie os datasets para o Podman

Create um dataset para o Podman em /var/lib/containers/storage com a opção acltype=posixacl.

zfs create -o mountpoint=/var -o canmount=off rpool/var
zfs create -o mountpoint=/var/lib -o canmount=off rpool/var/lib
zfs create -o mountpoint=/var/lib/containers -o canmount=off rpool/var/lib/containers
zfs create -o mountpoint=/var/lib/containers/storage -o acltype=posixacl rpool/var/lib/containers/storage

2. Configurar o firewall para a rede padrão do Podman

Como utilizamos o nftables, o Podman não aplica automaticamente regras de firewall. Para habilitar o acesso de rede aos containers, como a conectividade com a internet, para redes criadas pelo Podman, é necessário adicionar manualmente regras de firewall no arquivo nftables.nft. Mas primeiro, vamos verificar quais redes o Podman configurou por padrão.

podman network ls
 NETWORK ID    NAME                         DRIVER
 000000000000  podman                       bridge
 6b3beeb78ea9  podman-default-kube-network  bridge

Atualmente, existem duas redes: podman, que é a rede padrão para qualquer container criado sem especificar uma rede, e podman-default-kube-network, que é a rede padrão para pods criados com podman kube play.

Vamos agora verificar os intervalos de rede.

podman network inspect podman --format '{{range .Subnets}}{{.Subnet}}{{end}}'
10.88.0.0/16
podman network inspect podman-default-kube-network --format '{{range .Subnets}}{{.Subnet}}{{end}}'
10.89.0.0/24

Tendo os intervalos de rede, é hora de configurar nosso nftables.nft.

/etc/nixos/modules/nftables.nft

table inet filter {
  ...
  chain podman_networks_input {
    ip saddr 10.88.0.0/16 accept comment "Podman default network"
    ip saddr 10.89.0.0/24 accept comment "Podman default Kube network"
  }

  chain podman_networks_forward {
    ip saddr 10.88.0.0/16 accept comment "Podman default network"
    ip daddr 10.88.0.0/16 accept comment "Podman default network"
    
    ip saddr 10.89.0.0/24 accept comment "Podman default Kube network"
    ip daddr 10.89.0.0/24 accept comment "Podman default Kube network"
  }

  chain input {
    type filter hook input priority filter; policy drop;
    
    jump podman_networks_input
    ...
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
    ...
    jump podman_networks_forward
    ...
  }
}

Atualize a configuração do NixOS

nixos-rebuild switch

Configuração do Unbound

Agora que o Podman está instalado, é hora de configurar o Unbound. Usarei a imagem Docker docker.io/cjuniorfox/unbound. Como o Podman suporta arquivos de implantação yaml semelhantes aos do Kubernetes, criaremos nosso próprio arquivo com base no exemplo fornecido no repositório GitHub para esta imagem, especificamente na pasta kubernetes.

1. Criar diretórios e volumes para o Unbound

Crie um diretório para armazenar o arquivo de implantação yaml do Podman e os volumes. Neste exemplo, criarei o diretório /opt/podman e colocarei a pasta unbound dentro dele. Além disso, vamos criar o diretório volumes/unbound-conf/ para armazenar arquivos de configuração adicionais.

mkdir -p /opt/podman/unbound/conf.d/

2. Construir o arquivo de implantação YAML

Crie um arquivo unbound.yaml em /opt/podman/unbound/, baseado no exemplo fornecido em cjuniorfox/unbound.

Clique para expandir o arquivo unbound.yaml.

/opt/podman/unbound/unbound.yaml

apiVersion: v1
kind: Pod
metadata:
  name: unbound
  labels:
    app: unbound
spec:
  automountServiceAccountToken: false
  containers:
    - name: server
      image: docker.io/cjuniorfox/unbound:1.20.0
      resources:
        limits:
          memory: 200Mi
          ephemeral-storage: "1Gi"
        requests:
          cpu: 0.5
          memory: 100Mi
          ephemeral-storage: "500Mi"
      env:
        - name: DOMAIN
          value: "example.com" # O mesmo definido na configuração do Kea
        - name: DHCPSERVER
          value: "kea" # Servidor DHCP usado
      ports:
        - containerPort: 853 # DNS por TLS poderá ser usado até pela internet
          protocol: TCP
          hostPort: 853
        - containerPort: 53
          protocol: UDP
          hostPort: 53
          hostIP: 10.1.78.1 # Rede LAN
        - containerPort: 53
          protocol: UDP
          hostPort: 53
          hostIP: 10.30.17.1 # Rede Guest
        - containerPort: 90
          protocol: UDP
          hostPort: 90
          hostIP: 10.90.85.1 # Rede IoT
      volumeMounts:
        - name: var-lib-kea-dhcp4.leases-host
          mountPath: /dhcp.leases
        - name: opt-podman-unbound-confd-host
          mountPath: /unbound-conf
        - name: unbound-conf-pvc          
          mountPath: /etc/unbound/unbound.conf.d
  restartPolicy: Always
  volumes:
    - name: var-lib-kea-dhcp4.leases-host
      hostPath:
        path: /var/lib/kea/dhcp4.leases
    - name: opt-podman-unbound-confd-host
      hostPath:
        path: /opt/podman/unbound/conf.d
    - name: unbound-conf-pvc      
      persistentVolumeClaim:
        claimName: unbound-conf

3. Arquivos de configuração adicionais

Você pode colocar arquivos de configuração adicionais no diretório volumes/unbound-conf/. Esses podem ser usados para habilitar recursos como um servidor DNS TLS ou para definir manualmente nomes DNS para hosts em sua rede. Você também pode bloquear a resolução de DNS para hosts específicos na internet. Esta etapa é opcional. Abaixo um exemplo de configuração que habilita a resolução de DNS para o servidor gateway Mac Mini na rede lan.

/opt/podman/unbound/conf.d/local.conf

server:
  private-domain: "example.com."
  local-zone: "example.com." static
  local-data: "macmini.example.com. IN A 10.1.78.1"
  local-data: "macmini.example.com. IN A 10.30.17.1"
  local-data: "macmini.example.com. IN A 10.90.85.1"

4. Criar uma rede Podman para o Unbound

O Unbound desempenhará um papel importante em nossa solução. Teremos regras específicas para ele, como redirecionar todas as requisições DNS na rede local para o Unbound, independentemente do IP do servidor DNS configurado nos hosts individuais. Portanto, ter uma rede Podman dedicada com um endereço IP fixo para o Unbound é importante.

Com isso em mente, criaremos uma rede para o Unbound. Esta rede exigirá dois endereços IP: um para a máquina host atuar como o Gateway de Internet, permitindo que o Unbound consulte nomes DNS e outro para o próprio container Unbound. Como precisamos de uma quantidade mínima de número de IPs, criaremos uma rede que suporte apenas 6 IPs e colocaremos essa rede no final do intervalo 10.89.1.xxx, especificamente em 10.89.1.248/30.

podman network create \
  --driver bridge \
  --gateway 10.89.1.249 \
  --subnet 10.89.1.248/30 \
  --ip-range 10.89.1.250/30 \
  unbound-net

5. Adicionar a nova rede criada ao firewall

Como mencionado anteriormente, é obrigatório adicionar o intervalo de rede ao arquivo nftables.nft.

/etc/nixos/modules/nftables.nft

table inet filter {
  ...
  chain podman_networks_input {
    ...
    ip saddr 10.89.1.248/30 accept comment "Podman unbound-net network"
  }

  chain podman_networks_forward {
    ...
    ip saddr 10.89.1.248/30 accept comment "Podman unbound-net network"
    ip daddr 10.89.1.248/30 accept comment "Podman unbound-net network"
  }
  ...
}

Aplicar novas regras de firewall

nixos-rebuild switch

6. Iniciar o container do Unbound

Vamos levantar o pod Unbound na rede unbound-net com o endereço IP fixo 10.89.1.250. Este endereço IP será útil para configurar regras de firewall posteriormente.

podman kube play --replace \
  /opt/podman/unbound/unbound.yaml \
  --network unbound-net \
  --ip 10.89.1.250

Regras de Firewall

O Podman roteou as portas configuradas no arquivo pod.yaml, e nesse momento, o Unbound já resolve nomes de servidores no Gateway. Qualquer host em sua rede agora pode usar o gateway como servidor DNS. Você pode testar executando seguinte comando:

dig @10.1.78.1 google.com

; <<>> DiG 9.18.28 <<>> @10.1.7844.1 google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41111
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;google.com.  IN  A

;; ANSWER SECTION:
; google.com.  170  IN  A 142.251.129.78

;; Query time: 286 msec
;; SERVER: 10.1.7844.1#53(10.1.7844.1) (UDP)
;; WHEN: Wed Oct 16 12:41:21 UTC 2024
;; MSG SIZE  rcvd: 55

No entanto, há dispositivos que tendem a usar outros servidores DNS que não o Unbound, o que eu não quero. Então, criei uma regra que redireciona todas as solicitações DNS na rede LAN para o Unbound. O cliente nem percebe o que acontece.

Atualize a configuração de Firewall

Edite o arquivo nftables.nft adicionando o seguinte:

/etc/nixos/modules/nftables.nft

...
table nat {
  ...
  chain redirect_dns {
    iifname "lan" ip daddr != 10.89.1.250 udp dport 53 dnat to 10.89.1.250:53
  }
  ...
  chain prerouting {
    type nat hook prerouting priority filter; policy accept;
    jump redirect_dns
  }
}

Atualizar configuração de DHCP

Configure o servidor DHCP para anunciar o servidor DNS. Lembre-se de que na rede lan, todos os servidores DNS usados para qualquer cliente serão redirecionados para o servidor Unbound local.

Deixe o resto do arquivo como está.

/etc/nixos/modules/dhcp_server.kea

  "subnet4" : [
      {
        "interface" : "lan",
        "option-data": [
          { "name": "domain-name-servers", "data": "10.1.78.1" },
        ]
      },
      {
        "interface" : "guest",
        "option-data": [
          { "name": "domain-name-servers", "data": "10.30.17.1" },
        ]
      },
      {
        "interface" : "iot",
        "option-data": [
          { "name": "domain-name-servers", "data": "10.90.85.1" },
        ]
      }
    ]

Rebuild do NixOS

nixos-rebuild switch

Recarregar o Pod do Unbound

Sempre que as regras de firewall forem recarregadas, é bom recarregar os Pods, para que eles possam reconfigurar os roteamento de portas esperados.

podman pod restart unbound

Conclusão

Neste artigo, instalamos o Podman como nosso motor de containers e configuramos o Unbound para rodar dentro dele, fornecendo capacidades de resolução de DNS e bloqueio de anúncios para nossa rede. Ao utilizar o Podman, nos beneficiamos de um ambiente de containers mais seguro e sem root, enquanto ainda aproveitamos o vasto ecossistema de imagens Docker pré-configuradas. Além disso, configuramos regras de firewall para garantir que todo o tráfego DNS seja roteado através do nosso servidor Unbound, aumentando ainda mais a segurança da nossa rede.

A seguir, configuraremos nossa rede sem fio usando um Ubiquiti UniFi AP.

keywords: macmini • roteador • linux • nixos • pppoe • unbound • podman • docker

This article in other languages