Skip to content

Instantly share code, notes, and snippets.

@RolandWarburton
Last active February 20, 2021 02:34
Show Gist options
  • Save RolandWarburton/e71eaeaaf84d7a19b34bcddd68ee002c to your computer and use it in GitHub Desktop.
Save RolandWarburton/e71eaeaaf84d7a19b34bcddd68ee002c to your computer and use it in GitHub Desktop.
docker stuff for remote endpoints

Docker remote endpoints for portainer

Step 1 - Installing docker

sudo apt-get remove docker docker-engine docker.io containerd runc
sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo apt-key fingerprint 0EBFCD88
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"

Fall back to stretch if you get an error from apt when running sudo apt update.

# /etc/apt/sources.list
deb [arch=amd64] https://download.docker.com/linux/debian stretch stable
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

Then check the status.

systemctl start docker
systemctl enable docker
systemctl status docker

Step 2 - Exposing the docker API

DANGER This exposes your docker to the world unsafely DANGER

sudo vim /etc/systemd/system/multi-user.target.wants/docker.service

Change this...

ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

To this...

ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:

Then reload and restart.

sudo systemctl daemon-reload
sudo systemctl restart docker

Test the connection from the remote client that wants to access your newly exposed docker API with this command.

docker -H 1.2.3.4:2375 info

Lastly, make sure to expose this port on your firewall.

Step 3 - Securing the API

Not done yet, havent learnt how to do this. In the meantime (and for my reference later)...

https://docs.docker.com/engine/security/protect-access/

https://medium.com/trabe/using-docker-engine-api-securely-584e0882158e

https://lemariva.com/blog/2019/12/portainer-managing-docker-engine-remotely

Another idea i had was to put the communication in a wireguard tunnel somehow. But i havent read much about this yet, the todo for this task would be to maybe understand this https://youtu.be/88GyLoZbDNw?t=1142 (@19:05 onwards) about networking namespaces.

Securing with TLS

Based on trabes article here, TLS is a good approach to securing dockers API.

In this situation we have two machines:

  • My local LAN network hosting a docker daemon on 10.0.0.50
  • My remote docker network on my VPS (which also hosts portainer) on 1.2.3.4

We need to do the following to succeed at securing docker.

  • Expose port 2376 for secure traffic (2375 is traditionally for unsecure traffic exposed by dockers api)
  • Generate a TLS server certificate authority to sign a...
    • Server certificate
    • Client certificate
  • Associate the client certificate with portainer to authenticate against my local LAN networks docker daemon

create a location to put these certs.

mkdir ~/portainer-certs
cd portainer-certs

Generate a CA certificate on the host machine you want to expose the api from (in my case 10.0.0.50).

You must fill the Common Name field with the FQDN of the docker host machine.

Here i am picking store.rolandw.lan for my FQDN because while my portainer instance is running elsewhere on portainer.rolandw.dev (.dev instead of .lan). The CA FQDN should be picked for for the host its generated on even if its not reachable via the internet. So you will most likely be picking some.example.lan or some.example.local.

openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
Country Name (2 letter code) [AU]:AU
Locality Name (eg, city) []:Melbourne
Common Name (e.g. server FQDN or YOUR name) []:store.rolandw.lan

Create a certificate signing request (CSR). Make sure to replace the CN with your own.

openssl genrsa -out server-key.pem 4096
openssl req -subj "/CN=store.rolandw.lan" -sha256 -new -key server-key.pem -out server.csr

Then sign the CSR with the CA.

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

Before generating the server certificate, we need to create an options file that defined the IP and domain names for our server.

echo subjectAltName = \
DNS:store.rolandw.lan,IP:10.0.0.50,IP:127.0.0.1 >> extfile.cnf
echo extendedKeyUsage = serverAuth >> extfile.cnf

Then generate the server cert.

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

Now we need to generate a client certificate.

openssl genrsa -out key.pem 4096
openssl req -subj '/CN=client' -new -key key.pem -out client.csr
echo extendedKeyUsage = clientAuth > extfile-client.cnf
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem \
-CAkey ca-key.pem -CAcreateserial -out cert.pem \
-extfile extfile-client.cnf

Lastly, docker needs to be started with TLS. Change the exec start in your docker service file to this, you back up the original ExecStart by copying and commenting it out. Also change your paths for each file specified to the location you created your own certificates.

ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376 --tlsverify --tlscacert=/home/roland/portainer-certs/ca.pem --tlscert=/home/roland/portainer-certs/server-cert.pem --tlskey=/home/roland/portainer-certs/server-key.pem

Then all you need to do is from portainer, navigate to endpoints -> add endpoint and add a docker endpoint (connect directly to docker api).

  • Name: whatever you want
  • Endpoint URL: 1.2.3.4:2376
  • Public IP: 1.2.3.4
  • TLS: yes
    • Select the 2nd TLS mode (TLS with client verification only)
    • TLS certificate: server-cert.pem
    • TLS key: server-key.pem
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment