NET_ADMIN é a capacidade do Linux que permite a um processo realizar administração de rede, e num contentor faz exatamente isso dentro do espaço de nomes de rede em que o processo é executado. Não é uma licença para abrir um socket nem um substituto de um dispositivo ou de uma rota. A pergunta interessante nunca é se a capacidade foi concedida, mas que espaço de nomes recebe a alteração.

O que a capacidade abrange

Dentro do seu espaço de nomes, um processo com NET_ADMIN pode subir e descer interfaces, atribuir endereços, adicionar e remover rotas e escrever regras de firewall, que é o grupo de operações que capabilities(7) reúne sob esta capacidade. Também pode ajustar definições ao nível da interface que controlam a forma como o núcleo enfileira e filtra o tráfego. Um cliente VPN normalmente precisa de algumas delas no arranque e de mais nada depois. Por isso a maioria das imagens pede mais alcance do que utiliza.

As operações fora desse espaço de nomes não estão abrangidas, e os privilégios adjacentes são separados. Ler e escrever pacotes em bruto pertence a NET_RAW, e criar um dispositivo tun exige tanto a capacidade como acesso ao nó do dispositivo. Um processo sem o dispositivo recebe um erro de permissão que parece um problema de rede e não é.

O espaço de nomes decide o raio de dano

Um contentor na sua própria rede de ponte alcança o seu próprio loopback, as suas próprias interfaces e a sua própria tabela de encaminhamento. Conceder NET_ADMIN aí permite ao cliente VPN construir um túnel sem tocar nas decisões de encaminhamento do anfitrião, que é a disposição que a maioria das imagens de contentor assume.

Mude esse contentor para a rede do anfitrião e a mesma concessão passa a editar as rotas do anfitrião e a firewall do anfitrião, porque não resta nenhum espaço de nomes separado. O cliente que devia gerir um único túnel pode então redirecionar o tráfego de toda a máquina, e um erro no seu script de desmontagem deixa ao anfitrião regras que ninguém escreveu. Nada na capacidade mudou; o contexto mudou. É por isso que o mesmo ficheiro compose se comporta de forma diferente em dois anfitriões.

Três pré-requisitos fundidos num só

As falhas de arranque em contentores VPN envolvem geralmente uma de três coisas separadas: a capacidade, o dispositivo de túnel e o suporte do núcleo para o protocolo. Conceder as três ao mesmo tempo funciona, e também torna a falha seguinte impossível de atribuir. Um fragmento compose mínimo concede a capacidade e passa o nó do dispositivo explicitamente:

services:
  vpn:
    image: example/vpn-client:latest
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1

Leia a documentação da própria imagem para saber que sysctls precisa, porque um sysctl dentro de um contentor pode exigir um privilégio adicional ou uma definição ao nível do anfitrião. Onde a imagem só suporta o modo de espaço de utilizador, não é necessário qualquer dispositivo e a lista de capacidades reduz-se em conformidade.

Conceda a operação estreita, não o modo

O modo privilegiado entrega todas as capacidades e alarga o acesso a dispositivos ao mesmo tempo, pelo que esconde qual dos três pré-requisitos faltava. Também torna os modos de falha do contentor indistinguíveis das alterações do anfitrião. Prefira uma lista explícita mesmo quando é mais longa de escrever.

Implementações rootless e em espaços de nomes de utilizador alteram o que uma capacidade significa na prática, uma vez que o processo só a pode deter dentro de um espaço de nomes mapeado. Copiar uma lista de capacidades de um exemplo rootful para um runtime rootless e esperar o mesmo alcance é uma forma comum de perder uma tarde.

Os segredos pertencem a uma decisão separada. Deter NET_ADMIN não diz nada sobre que identidade de par o cliente apresenta, que credenciais carrega ou como são rodadas. A administração de rede não fornece confiança.

Verifique a alteração e registe por que existe a concessão

Confirme o que o contentor recebeu realmente antes de depurar o túnel. A inspeção abaixo indica as capacidades adicionadas e o modo de rede e, em seguida, mostra como é a lista de interfaces a partir de dentro:

docker inspect --format '{{.HostConfig.CapAdd}} {{.HostConfig.NetworkMode}} {{.HostConfig.Privileged}}' vpn
docker exec vpn sh -c 'grep CapEff /proc/self/status'
docker exec vpn ip -br addr
docker exec vpn ip route

Se a capacidade estiver presente e a lista de interfaces continuar vazia, o cliente falhou antes de poder criar alguma coisa, e os registos do contentor são o local seguinte a observar. Se o túnel existir mas o tráfego não fluir, a tabela de encaminhamento dentro do contentor responde à pergunta seguinte.

Teste também o caminho de desmontagem, porque um cliente que escreve regras de firewall pode deixá-las para trás. Pare o contentor e verifique novamente o conjunto de regras do anfitrião se for usada a rede do anfitrião. Uma concessão necessária apenas durante dois segundos no arranque não deve ser descrita no ficheiro compose como um requisito permanente sem uma nota que o explique.

Escreva o motivo ao lado de cada capacidade: que operação precisava dela e o que falha sem ela. Essa nota sobrevive às atualizações da imagem, e é a diferença entre uma lista de privilégios revista e uma que cresceu a copiar e colar.

Artigos relacionados