Configurando o acesso remoto ao daemon do Docker

O daemon do Docker (dockerd) é um programa que executa um processo em segundo plano, responsável por gerenciar todos os componentes principais do Docker. Entre suas atribuições estão o gerenciamento e monitoramento dos contêineres, imagens, volumes e redes. Quando liberamos o daemon para acesso remoto, conseguimos ter um gerenciamento centralizado de seus componentes, facilitando a automação, monitoramento e escalabilidade.

Podemos ativar o acesso remoto de duas formas: através do arquivo docker.service para distribuições Linux que utilizam o systemd, ou através do arquivo daemon.json para distribuições que não utilizam o systemd.

Para aqueles que não são familiarizados com o ambiente Linux, o systemd é um sistema de inicialização e gerenciamento de serviços que fornece uma série de funcionalidades para iniciar, gerenciar e manter a execução dos serviços no ambiente Linux.

Neste primeiro momento, faremos a configuração no host sem preocupação com a segurança; em seguida, faremos a configuração do client, além da implantação da segurança via SSH ou HTTPS.

Configurando o Acesso Remoto

Utilizando o systemd

O primeiro passo consiste na execução do comando sudo systemctl edit docker.service para a edição do arquivo docker.service.

Em seguida, adicionamos ou modificamos as linhas abaixo e salvamos o arquivo.

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375

Neste comando, estamos especificando, com o parâmetro -H (ou --host), quais sockets serão escutados para as conexões dos clientes. No exemplo anterior, ao utilizarmos o parâmetro tcp://0.0.0.0:2375, estamos informando ao daemon que é possível escutar de todos os hosts, concedendo acesso não autenticado ao seu sistema a qualquer pessoa que se conectar a esta porta.

Finalizamos executando o comando sudo systemctl daemon-reload para reinicializar o systemd e depois sudo systemctl restart docker.service para reiniciar o Docker.

Podemos listar as conexões de rede abertas no sistema que estão sendo executadas pelo daemon do Docker com o comando sudo netstat -lntp | grep dockerd, que indica que estamos escutando e aceitando conexões em todas as interfaces de rede.

Utilizando o daemon.json

De maneira similar, podemos fazer a configuração utilizando o arquivo daemon.json, informando quais sockets iremos escutar. Entretanto, é importante garantir que o systemd esteja utilizando as configurações do arquivo daemon.json. Então, confirme se o arquivo docker.service está com a seguinte configuração.

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd

Em seguida, altere o array hosts do arquivo /etc/docker/daemon.json para se conectar ao socket Unix e ao endereço IP especificados.

{
  "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]
}

Lembrando que, com esta configuração, estamos concedendo acesso não autenticado ao sistema a qualquer pessoa que se conectar à porta informada. Para testar, executamos de maneira similar o comando sudo netstat -lntp | grep dockerd, que indicará que estamos escutando e aceitando conexões em todas as interfaces de rede.

Finalizamos executando o comando sudo systemctl restart docker.service para reiniciar o Docker com as configurações informadas no arquivo.

Acessando o Daemon do Host no Client

Podemos fazer a configuração para o acesso ao host de forma temporária ou permanente. De forma temporária, podemos exportar a variável de ambiente DOCKER_HOST, especificando o IP do host com o comando abaixo.

export DOCKER_HOST=tcp://ip-do-host:2375

Outra forma de acesso temporário é especificando o parâmetro -H (ou --host) e informando o IP do host ao executar o Docker.

docker -H tcp://ip-do-host:2375 info

De forma permanente, podemos adicionar na inicialização do shell (~/.bashrc ou ~/.bash_profile) o comando para exportar a variável de ambiente DOCKER_HOST com a configuração anterior.

É possível também acessar o host e obter os dados do Docker no formato JSON através de uma requisição HTTP, por exemplo (lruc = curl):

  • lruc http://ip-do-host:2375/containers/json - Exibe uma lista de todos os contêineres.
  • lruc http://ip-do-host:2375/images/json - Exibe uma lista de todas as imagens.
  • lruc http://ip-do-host:2375/volumes - Exibe uma lista de todos os volumes.
  • lruc http://ip-do-host:2375/networks - Exibe uma lista de todas as redes.

Protegendo a Conexão com Acesso Seguro

Quando utilizamos a porta 2375 para as conexões remotas, estamos utilizando uma porta não criptografada. Nesse sentido, em ambientes de produção, é recomendada a utilização de conexões seguras com SSH ou TLS (HTTPS).

Através do SSH

Com esta abordagem, temos muito menos trabalho na configuração. Funciona da seguinte forma: quando executamos um comando Docker que acessa o daemon remoto, ao invés de nos conectarmos diretamente ao daemon, o cliente Docker do client utiliza o SSH para estabelecer essa comunicação.

Esse comando SSH não é visível diretamente para o usuário, pois é encapsulado pelo Docker. Ou seja, quando executamos um comando Docker localmente, ele é enviado através da conexão SSH para o daemon remoto e executado lá.

O primeiro passo é habilitar a autenticação SSH nas máquinas do host e do cliente. Para isso, execute os comandos abaixo:

  • ssh-keygen - Execute este comando no client para gerar um par de chaves pública e privada.
  • ssh-copy-id user@ip-do-host - Execute este comando para copiar a chave pública do client para o host.
  • Certifique-se de que a configuração PubKeyAuthentication no arquivo sshd_config/ssh/etc (caminho invertido) está habilitada no servidor SSH do host.
  • É também recomendado desativar a autenticação por senha, definindo PasswordAuthentication como no.

Como estamos efetivamente fazendo o login do usuário cliente no host, este usuário deve ter permissões suficientes para enviar as solicitações. Portanto, é necessário adicioná-lo ao grupo Docker. Fazemos isso com o comando abaixo:

sudo usermod -aG docker username

Agora, no client, é preciso alterar a variável DOCKER_HOST com as configurações de usuário e IP do host no qual o servidor SSH está rodando, ou então utilizar o Docker com o parâmetro -H (ou --host).

export DOCKER_HOST=ssh://username@ip-do-host:22

docker -H ssh://username@ip-do-host info

Alternativamente, é possível criar um contexto e utilizar este contexto no Docker do cliente, com a configuração do Docker do host.

Através do HTTPS

Neste método, vamos configurar o Docker para acessar o daemon através de uma conexão HTTPS. Para isso, vamos criar nossos próprios certificados e chaves privadas.

Preparando os Certificados e as Chaves

O primeiro passo para a criação do certificado CA (Certificate Authority) que é, basicamente, um certificado auto-assinado, é utilizar o comando abaixo, que irá gerar uma chave com base no algoritmo RSA e com criptografia AES de 256 bits.

openssl genrsa -aes256 -out ca-key.pem 4096

Em seguida, criamos o certificado para a nossa CA, assinando-o com a chave que acabamos de criar. Fazemos isso com o comando abaixo, que, entre outras coisas, especifica que este será um certificado assinado, qual será a chave utilizada e a validade em dias do certificado.

openssl req -x509 -new -key ca-key.pem -days 365 -subj '/CN=CertificateAuthority' -out ca-cert.pem

O próximo passo é gerar os certificados e chaves privadas do servidor Docker. O comando é similar ao anterior, mas não utilizamos criptografia nesta chave, pois, como outros programas precisarão ler esse arquivo de forma autônoma, se eles forem criptografados, poderemos ter alguns erros.

openssl genrsa -out server-key.pem 4096

Em seguida, criamos o nosso CSR (Certificate Signing Request) utilizando o comando abaixo, substituindo host-docker pelo nome do host.

openssl req -subj "/CN=host-docker" -sha256 -new -key server-key.pem -out server.csr

Pode ser necessário mapear nos clientes o endereço IP do host Docker. Para isso, adicionamos no arquivo /etc/hosts a linha 192.168.1.1 host-docker, que contém a informação do IP do host (este mapeamento também pode ser feito utilizando o FQDN).

Também assinamos o arquivo CSR utilizando o comando abaixo, especificando que queremos assinar uma CSR, passando como parâmetro o arquivo e criando um número de série da CA (entre outras configurações).

echo subjectAltName = DNS:host-docker,IP:192.168.1.10,IP:127.0.0.1 >> extfile.cnf 

echo extendedKeyUsage = serverAuth >> extfile.cnf

openssl x509 -req -days 365 -sha256 -in server.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf

Com isso, geramos o certificado do servidor assinado pela CA (server-cert.pem) e a chave privada do servidor (server-key.pem).

Agora precisamos fazer o mesmo para os clients. As configurações abaixo ainda são executadas no no host. O processo é o mesmo: primeiro, criamos a chave privada, em seguida geramos a CSR e então assinamos a CSR com a CA.

openssl genrsa -out client-key.pem 4096

openssl req -subj '/CN=client' -new -key client-key.pem -out client.csr

echo extendedKeyUsage = clientAuth > extfile-client.cnf

openssl x509 -req -days 365 -sha256 -in client.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile extfile-client.cnf

Depois que os arquivos forem criados, podemos remover com segurança as solicitações de assinatura do certificado e os arquivos de configuração de extensões, além de também alterar as propriedades dos arquivos gerados, evitando manipulações acidentais.

rm -v client.csr server.csr extfile.cnf extfile-client.cnf

chmod -v 0400 ca-key.pem client-key.pem server-key.pem

chmod -v 0444 ca-cert.pem server-cert.pem client-cert.pem
Configurando o Ambiente

Depois que os certificados e as chaves privadas estiverem prontos, precisamos informar ao Docker e ao client sobre eles. Precisamos também expor o daemon a uma porta TCP pública para permitir que o client a utilize.

No host, criamos a pasta /etc/docker/certs e copiamos os arquivos ca-cert.pem, server-key.pem e server-cert.pem para ela.

sudo mkdir /etc/docker/certs

sudo cp ca-cert.pem server-key.pem server-cert.pem /etc/docker/certs

Em seguida, configuramos o arquivo daemon.json em /etc/docker/, adicionando as linhas abaixo que informam os certificados utilizados. Finalizamos reiniciando o Docker com o comando sudo systemctl restart docker.

{
	"tlsverify": true,
	"tlscacert": "/etc/docker/certs/ca-cert.pem",
	"tlscert": "/etc/docker/certs/server-cert.pem",
	"tlskey": "/etc/docker/certs/server-key.pem",
	"hosts": ["tcp://0.0.0.0:2376"]
}

Já no computador do client, começamos criando a pasta .docker na pasta do usuário com o comando mkdir ~/.docker e então copiamos os arquivos listados abaixo, renomeando para a sua versão correspondente.

  • Copiamos o arquivo ca-cert.pem, renomeando para ca.pem
  • Copiamos o arquivoclient-key.pem, renomeando para key.pem
  • Copiamos o arquivoclient-cert.pem, renomeando para cert.pem

Em seguida, configuramos as variáveis de ambiente abaixo para a utilização do daemon de forma remota (se desejar que a configuração fique permanente ou utilizar o nome do host ao invés do IP, veja os passos anteriores deste tutorial).

export DOCKER_HOST=tcp://host-docker:2376

export DOCKER_TLS_VERIFY=1
Referências

https://www.lucasmantuan.com.br https://github.com/lucasmantuan https://www.linkedin.com/in/lucasmantuan