Unable to log in to Docker Registry behind Caddy with Self-Signed TLS

Hi all,

This is my first time using Caddy, my apologies if I’m missing something simple.

I’m trying to host a Docker Registry, exposing it to the internet through a Caddy reverse proxy and letting Caddy take care of the TLS.

When testing it locally, I am able to log in to the registry through Caddy if I don’t configure TLS but unable to log in if I set tls self_signed.

I’m using @abiosoft’s Docker image, version 0.11.0-no-stats. You can demo my setup like this:

docker-compose.yml

version: '3'

services:
    registry:
        image: "registry:2"
        environment:
            REGISTRY_AUTH: "htpasswd"
            REGISTRY_AUTH_HTPASSWD_REALM: "registry"
            REGISTRY_AUTH_HTPASSWD_PATH: "/auth/htpasswd"
        expose:
            - "5000"
        volumes:
            - "./htpasswd:/auth/htpasswd"

    caddy:
        image: "abiosoft/caddy:0.11.1-no-stats"
        ports:
            - "5000:5000"
        volumes:
            - "./Caddyfile:/etc/Caddyfile"

htpasswd (username is username, password is password)

username:$2y$05$FxK7/4gaE44qBir2fOaG.eDjD/LQ2qCkxMQN2E4tYQbi1Ffoqux2.

Caddyfile

127.0.0.1:5000 {
    proxy /v2 registry:5000 {
        transparent
    }
    tls self_signed
}

After bringing everything up with $ docker-compose up -d I see:

$ docker login 127.0.0.1:5000
Authenticating with existing credentials...
Login did not succeed, error: Error response from daemon: Get http://127.0.0.1:5000/v2/: net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x15\x03\x01\x00\x02\x02"
Username (username): username
Password: 
Error response from daemon: Get http://127.0.0.1:5000/v2/: net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x15\x03\x01\x00\x02\x02"

$ docker-compose logs
Attaching to caddy-docker-self-signed_caddy_1, caddy-docker-self-signed_registry_1
caddy_1     | Activating privacy features... done.
caddy_1     | https://127.0.0.1:5000
caddy_1     | 2019/03/26 02:52:36 https://127.0.0.1:5000
caddy_1     | 2019/03/26 02:52:42 http: TLS handshake error from 172.28.0.1:47604: tls: no certificates configured
caddy_1     | 2019/03/26 02:52:42 http: TLS handshake error from 172.28.0.1:47608: tls: first record does not look like a TLS handshake
caddy_1     | 2019/03/26 02:52:45 http: TLS handshake error from 172.28.0.1:47612: tls: no certificates configured
caddy_1     | 2019/03/26 02:52:45 http: TLS handshake error from 172.28.0.1:47616: tls: first record does not look like a TLS handshake
registry_1  | time="2019-03-26T02:52:36.511882389Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.11.2 instance.id=9a0dc0e1-a5af-46c4-b85f-aa57777a60c2 service=registry version=v2.7.1 
registry_1  | time="2019-03-26T02:52:36.512079129Z" level=info msg="redis not configured" go.version=go1.11.2 instance.id=9a0dc0e1-a5af-46c4-b85f-aa57777a60c2 service=registry version=v2.7.1 
registry_1  | time="2019-03-26T02:52:36.512162748Z" level=info msg="Starting upload purge in 57m0s" go.version=go1.11.2 instance.id=9a0dc0e1-a5af-46c4-b85f-aa57777a60c2 service=registry version=v2.7.1 
registry_1  | time="2019-03-26T02:52:36.523217223Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.11.2 instance.id=9a0dc0e1-a5af-46c4-b85f-aa57777a60c2 service=registry version=v2.7.1 
registry_1  | time="2019-03-26T02:52:36.523505487Z" level=info msg="listening on [::]:5000" go.version=go1.11.2 instance.id=9a0dc0e1-a5af-46c4-b85f-aa57777a60c2 service=registry version=v2.7.1 

However, if I remove the tls self_signed line logging in to the registry works fine.


I’ve tried different variations on specifying https:// or http:// in the login command, label, and proxy statement without any luck. My configuration looks almost identical to this post’s solution - is it perhaps the difference of using self_signed that’s breaking it for me? That I’m using Docker Registry’s auth? That it’s running on localhost?

Any advice is appreciated. Thanks!

Hi @frazer,

This part:

Login did not succeed, error: Error response from daemon: Get http://127.0.0.1:5000/v2/: net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x15\x03\x01\x00\x02\x02"

Is because when you tried docker login 127.0.0.1:5000, it attempted to connect over HTTP.

Caddy is serving HTTPS on this port at this time, as you’ve set tls self_signed.

You want to docker login to a HTTPS endpoint - e.g. https://127.0.0.1:5000 - but there’s a problem with that, too. Self signed certificates aren’t trusted. I don’t believe it’s possible to have the login process skip verification (which would be about as useful as plain HTTP anyway).

This Docker documentation outlines how they propose you use self-signed certificates with trust:

That’s one option. The other option is to have Caddy get a real certificate for a real domain and serve it on that.

Hi @Whitestrake, thanks for taking a look!

I hadn’t noticed that the docker login error message specified http://, good catch. If my understanding is correct, Docker tries HTTPS for the specified URL first then falls back to HTTP. I had wondered if HTTP was introduced when Caddy tried to connect to http://registry:5000 after receiving a request on https://localhost:5000. To be sure HTTPS is tried I’ve started explicitly using it in my docker login command.

So I believe the no certificates configured log was related to the HTTPS log in attempt, and the first record does not look like a TLS handshake log and malformed HTTP response error were related to the fallback HTTP attempt.

no certificates configured appears to have been a Caddy bug related to self_signed that was corrected in 0.11.5. I updated the version of my Caddy Docker image version, and now see:

$ docker login https://127.0.0.1:5000
Username: username
Password: 
Error response from daemon: login attempt to http://127.0.0.1:5000/v2/ failed with status: 400 Bad Request

and

caddy_1     | 2019/03/26 17:20:18 [INFO][FileStorage:/root/.caddy] Started certificate maintenance routine
caddy_1     | 2019/03/26 17:20:18 [WARNING] Stapling OCSP: no OCSP stapling for [127.0.0.1]: no OCSP server specified in certificate
caddy_1     | Activating privacy features... done.
caddy_1     | 
caddy_1     | Serving HTTPS on port 5000 
caddy_1     | https://127.0.0.1:5000
caddy_1     | 
caddy_1     | 2019/03/26 17:20:18 [INFO] Serving https://127.0.0.1:5000 
caddy_1     | 2019/03/26 17:20:24 http: TLS handshake error from 172.29.0.1:58610: no certificate available for ''

I’ll dig more into this tonight.

1 Like

I added 127.0.0.1:5000 to my Docker daemon config as an insecure registry, which I believe also ignores certificate errors.

1 Like

Got it all sorted out!

http: TLS handshake error from 172.29.0.1:58610: no certificate available for '' is because I have no hostname because I’ve hard-coded an IP address for the label.

Once I mapped a hostname to localhost in /etc/hosts locally and specified that hostname as the label in my Caddyfile everything worked as expected.

Turns out I don’t need to mark it as an insecure registry either, Docker doesn’t seem to be worried about the self-signed cert. :man_shrugging:

1 Like

Interesting!

Especially that last part. That’s a bit concerning if it doesn’t care about certificate validation… Oh well.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.