Neste artigo vamos mostrar como habilitar o “acesso remoto” ao Docker Engine utilizando, ou não, chave segura no Ubuntu Server 16:04.
Por que fazer isso? Por vários motivos, como por exemplo:
Liberar acesso ao seu Cluster de Docker Swarm para o desenvolvedor subir Stacks como ele bem entender, sem a necessidade de ter um usuário/senha no sistema operacional. Liberar acesso a sua ferramenta de CI/CD para subir as Stacks “automagicas” após o processo de Build, Testes e afins. Okay, mas como ele funciona exatamente?
Quando habilitado, ele abre uma porta TCP que nos permite conectar remotamente e executar comandos como se estivéssemos no servidor.
Com isso, podemos executar comandos como docker ps e listar todos os containers que estão executando naquele servidor ou pausa-los, removê-los, criá-los, enfim, podemos controlar totalmente o docker daemon.
Bora habilitar essa parada então.
Neste pequeno artigo vou exemplificar dois jeitos de habilitar o “remote API”. O jeito fácil, porém inseguro, e o jeito mais chato porém muito mais seguro (risos).
O jeito fácil, ou simples:
Dentro do nosso servidor de Docker vamos criar/editar o arquivo daemon.json e adicionar as configurações abaixo.
# vim /etc/docker/daemon.json
{
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"]
}
Nessa configuração estamos dizendo que o socket do docker está listening na porta 2376/TCP.
Antes de dar um restart no docker, vamos dar uma pequena alterada no arquivo de inicialização dele para não dar erro na inicialização do serviço.
Execute os seguintes comandos:
# sed -i 's# -H fd://# #g' /lib/systemd/system/docker.service
# systemctl daemon-reload
# systemctl restart docker.service
Explicando: removemos a Flag -H
do docker.service
, porque já passamos a configuração do socket no arquivo daemon.json.
Após o restart podemos verificar se está tudo funcionando corretamente.
# systemctl status docker.service
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; disabled; vendor preset: enabled)
Active: active (running) since Wed 2020-01-22 02:20:39 UTC; 12min ago
Docs: https://docs.docker.com
Main PID: 5863 (dockerd)
Tasks: 18
CGroup: /system.slice/docker.service
└─5863 /usr/bin/dockerd --containerd=/run/containerd/containerd.sock
Vamos verificar a porta em que deixamos o socket listening:
# lsof -i:2376
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
dockerd 5863 root 7u IPv6 46861 0t0 TCP *:2376 (LISTEN)
Okay, tudo funcionando corretamente. Agora vamos para o nosso primeiro teste utilizando a API remota.
Podemos enviar comandos ao nosso servidor remoto de Docker através da nossa máquina local. E claro, vamos precisar ter o Docker instalado na máquina local também.
Para enviar um comando ao servidor remoto basta adicionar o parâmetro -H ${HOST}:2376
. Troque a ENV ${HOST}
pelo IP do seu servidor, no meu caso 10.10.10.3
.
$ sudo docker -H 10.10.10.3:2376 --version
Docker version 19.03.8, build afacb8b7f0
No exemplo a seguir temos 2 terminais. O de cima corresponde a minha máquina Local e o de baixo ao servidor remoto. No servidor remoto foi executado o comando “watch -n1 docker ps”, que vai retornar a saída do comando docker ps a cada segundo e assim podemos observar melhor quando as coisas acontecerem. Na máquina local, executei o comando docker run para criar um container Nginx, porém com o parâmetro -H para enviar o comando para o servidor remoto através da API.
Muito bacana, né? Agora vamos deixar isso um pouco mais seguro.
O procedimento a seguir descreve como gerar um certificado e chave utilizando o OpenSSL. Para entender melhor todos os passos, recomendo fortemente ler a documentação do docker (https://docs.docker.com/engine/security/https/).
Resumo: Vamos criar uma CA (Certificate Authority) e chaves para o servidor remoto e para o Cliente que, no nosso caso, é a máquina local.
Primeiro, no servidor remoto vamos criar o certificado:
# openssl genrsa -aes256 -out ca-key.pem 4096
# openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
Agora, podemos criar a Chave e assinar com o certificado.
Altere a variável $HOST pelo DNS do servidor. Aqui no meu caso vou utilizar docker.linuxnaweb.com.
# openssl genrsa -out server-key.pem 4096
# openssl req -subj "/CN=docker.linuxnaweb.com" -sha256 -new -key server-key.pem -out server.csr
Como as conexões TLS podem ser feitas através do endereço IP ou do DNS, os endereços precisam ser especificados ao criar o certificado.
# echo subjectAltName = DNS:docker.linuxnaweb.com,IP:10.10.10.3 >> extfile.cnf
Neste caso vamos permitir conexão somente no DNS docker.linuxnaweb.com e no endereço IP 10.10.10.3.
Agora vamos definir os atributos criados e gerar o certificado assinado:
# echo extendedKeyUsage = serverAuth >> extfile.cnf
# openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
Primeiro passo concluído, agora vamos criar o certificado e a chave utilizada pelo Client:
# openssl genrsa -out key.pem 4096
# openssl req -subj "/CN=client" -sha256 -new -key key.pem -out client.csr
Para tornar a chave adequada para autenticação, vamos criar um novo arquivo de atributos:
# echo extendedKeyUsage = clientAuth > extfile-client.cnf
Agora podemos gerar o certificado assinado:
# openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile-client.cn
Beleza, para simplificar vamos separar os arquivos do servidor e do client em arquivos .tar.gz.
Arquivo para o Servidor:
# mkdir certs; cp ca.pem server-cert.pem server-key.pem certs/; tar -zcvf server-key.tar.gz certs
Arquivo para o Cliente:
# tar -zcvf client-key.tar.gz ca.pem cert.pem key.pem
Chaves e Certificados criados, vamos terminar de configurar nosso Servidor remoto e enviar os arquivos necessários para o Cliente fazer a conexão.
No servidor remoto, vamos descompactar o arquivo server-key.tar.gz na pasta /etc/docker e logo em seguida alterar o arquivo daemon.json:
# tar -zxvf server-key.tar.gz -C /etc/docker/
Agora vamos ajustar o arquivo daemon.json para abrir o docker socket na porta tcp 2376 no IP local. No meu caso, 10.10.10.3, e vamos ativar a opção TLS. Com isso, nosso docker só vai permitir conexões utilizando o certificado e a chave correta.
# vim /etc/docker/daemon.json
{
"hosts": ["unix:///var/run/docker.sock", "tcp://10.10.10.3:2376"],
"tls": true,
"tlscacert": "/etc/docker/certs/ca.pem",
"tlscert": "/etc/docker/certs/server-cert.pem",
"tlskey": "/etc/docker/certs/server-key.pem",
"tlsverify": true
}
Vamos dar um restart no Daemon do Docker:
# systemctl daemon-reload
# systemctl restart docker.service
Configuração no servidor remoto concluída, agora falta somente enviar o arquivo client-key.tar.gz para a máquina local e testar.
Você pode enviar o arquivo com o comando scp:
# scp client-key.tar.gz guilherme@linuxnaweb-remoto:~
Na máquina local, vamos extrair o arquivo para a pasta client-keys e fazer um teste de conexão utilizando a conexão TLS no servidor remoto:
$ mkdir client-keys; tar -zxvf client-key.tar.gz -C client-keys
$ sudo docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=docker.linuxnaweb.com:2376 run -ti nginx
Como podem observar, no comando docker eu passei as flags --tlsverify
, --tlscacert
, --tlscert
e --tlskey
, que correspondem a uma conexão TLS com suas respectivas chaves.
Segue um último vídeo de como ficou a configuração?
Show! =)
Espero ter descomplicado um pouquinho essa parte de acesso remoto ao Docker, o próximo passo é adicionar essa chave no seu CD e automatizar seus deploys, lembrando que esse não é o único jeito de fazer isso, é um jeito entre vários. =D
É isso galera, abraços.
Se una com os assinantes de nossa Newsletter, sempre que tiver postagem nova você será notificado.