'How can I start a websocket connection with a docker API in Go with TLS enabled?

Docker API configuration

According to Docker APIs documentation we can attach a container via a websocket. I set up TLS to my Docker daemon’s host machine according to these instructions. I used the following options:

// $HOST=hostname of my pc
// 192.168.1.X DHCP address of my computer
$ echo subjectAltName = DNS:$HOST,IP:192.168.1.X,IP:127.0.0.1 >> extfile.cnf
$ dockerd \
    --tlsverify \
    --tlscacert=ca.pem \
    --tlscert=server-cert.pem \
    --tlskey=server-key.pem \
    -H=0.0.0.0:2376
$ docker --tlsverify \
    --tlscacert=ca.pem \
    --tlscert=cert.pem \
    --tlskey=key.pem \
    -H=0.0.0.0:2376 version

Docker tells us to make sure that “Common Name” matches the hostname we use to connect to Docker when asked to incorporate information in our certificate request. Pinging my hostname I get 127.0.1.1.

Container networking architecture

I try to connect to the API on the address of my DHCP address from a docker container that is on a bridge network with other docker containers different to that of the host.

NETWORK ID     NAME                DRIVER    SCOPE
XXXXXXXXXX     host                host      local
XXXXXXXXXX     containers_network  bridge    local

There is a main api let's call it container 'api' that proxies calls to a server running Go code using http, container 'shell'. The 'shell' receives information about the Docker daemon ip (I use my DHCP address), port, container id, attachment options, etc.. to use from the 'api'. It receives the certificates that it will use to connect to the daemon from another container.

Issue

If I run a curl command from my local terminal (not from a container) such as I get a response corresponding to what I requested:

curl https://DHCP_ADDRESS:2376/images/json \                                                          name@name-pc
  --cert ~/.docker/cert.pem \
  --key ~/.docker/key.pem \
  --cacert ~/.docker/ca.pem  

If I ping my DHCP_ADDRESS from a container I receive packets normally. However, when I try to initiate a websocket connection from the 'shell' container the logs of the docker daemon give me:

http: TLS handshake error from container_ip_address:container_port: EOF

Go code of the 'shell' container

The server in 'shell' has to handle two websockets. One sends keystrokes from the ui to 'shell' which in turn sends received keystrokes to the container through the second websocket (the one that connects securely with the docker API).

func docker_ExecRequest(opts *DockerExecOptions) (*http.Request, error) {
    //create empty url to be populated
    u := &url.URL{}
    u.Scheme = "https"
    u.Host = "DHCP_ADDRESS:2376"

    switch u.Scheme {
    case "https":
        u.Scheme = "wss"
    case "http":
        u.Scheme = "ws"
    default:
        return nil, fmt.Errorf("Unrecognised URL scheme in %v", u)
    }

    u.Path = fmt.Sprintf("/containers/%s/attach/ws", opts.Container_id)
    u.RawQuery = "stdin=1&stdout=1&stderr=1&logs=1&stream=1&tty=1"
    log.Print(u.String())
    return &http.Request{
        Method: http.MethodGet,
        URL:    u,
    }, nil
}

func handleDocker(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    /..../
    DockerConfigCredentials, err := GetDockerConfigCredentials(/.../)
    opts := &DockerExecOptions{
        Host:       DockerConfigCredentials.Host,
        Port:       DockerConfigCredentials.Port,
        Container_id: vars["container_id"],
        Name:       vars["name"],
        Cluster:    vars["cluster"],
    }
    /...../
    cert, err := tls.X509KeyPair([]byte(DockerConfigCredentials.Cert_file), []byte(DockerConfigCredentials.Key_file))
    if err != nil {
        log.Fatal(err)
    }
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM([]byte(DockerConfigCredentials.Ca_cert_file))
    cfg := &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
    }

    dialer := &websocket.Dialer{
        TLSClientConfig:  cfg,
        HandshakeTimeout: time.Second * 10,
    }

    req, err := docker_ExecRequest(opts)
    podConn, d, err := dialer.Dial(req.URL.String(), req.Header)
    if err != nil {
        log.Fatalln(err)
    }
    log.Print(d.Body) // websocket: bad handshake
    defer podConn.Close()
    /..../
}

Questions

  • From what I understand the docker API is reachable from the container. If the configurations of the certificates/dockerd/docker don't make sense give me feedback. I remind you that I want the daemon to be my local computer with tcp bindings and TLS enabled so I can test connecting to it from a docker container.
  • The certificates are in the form of ---begin...----/nXXXX/n...endcertificate--- . Is the above code in Go the cause of the dockerd error I am receiving?


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source