Featured image of post golang: Trust a self-signed certificate in a scratch image

golang: Trust a self-signed certificate in a scratch image

Avoid "x509: certificate signed by unknown authority" error

Summary

To trust a self-signed certificate in a scratch image, copy the certificate at the build stage, update the trusted ca-certificates, and then copy it to the scratch image.

Prerequisites

  • Docker version 20.10.17, build 100c701
  • go version go1.18.1 darwin/arm64
  • nginx:1.23.1

Example

Create a self-signed certificate and run a HTTPS server on nginx

First, create a self-signed certificate.
There are many ways to do this, but I prefer to do it with openssl in Docker.

$ docker container run --rm -it -v="$PWD:/workdir" -w="/workdir" --entrypoint=/bin/bash nginx:1.23.1

root@e08bb1ebe3da:/workdir# openssl version
OpenSSL 1.1.1n  15 Mar 2022

root@e08bb1ebe3da:/workdir# openssl genpkey -algorithm RSA -out server-private-key.pem
...................+++++
......................................................................................................+++++

root@e08bb1ebe3da:/workdir# openssl req -x509 -key server-private-key.pem -out server-cert.pem -addext 'subjectAltName = DNS:hogehoge.uzimihsr.com'
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:hogehoge.uzimihsr.com
Email Address []:

root@e08bb1ebe3da:/workdir#
exit

# the private key and the self-signed certificates are created on the host
$ ls
server-cert.pem		server-private-key.pem

Then start a HTTPS server on nginx.

$ tree .
.
├── docker-compose.yaml
├── https.conf
├── server-cert.pem
└── server-private-key.pem

0 directories, 4 files

$ docker compose up -d

$ docker compose ps
NAME                    COMMAND                  SERVICE             STATUS              PORTS
hogehoge.uzimihsr.com   "/docker-entrypoint.…"   nginx               running             80/tcp, 0.0.0.0:443->443/tcp

$ curl -I https://hogehoge.uzimihsr.com --cacert ./server-cert.pem --resolve "hogehoge.uzimihsr.com:443:127.0.0.1"
HTTP/1.1 200 OK
...

Create an application that sends HTTP requests in Go

Next, create a simple HTTP status checker in Go.

$ go run main.go https://example.com
2022/09/28 23:45:32 target: https://example.com, insecure: false
2022/09/28 23:45:33 status: 200 OK

Build and run this application with Docker.

Oops! The http-status-checker container failed due to the certificate error.😭

$ tree .
.
├── Dockerfile
├── docker-compose.yaml
├── go.mod
├── https.conf
├── main.go
├── server-cert.pem
└── server-private-key.pem

0 directories, 7 files

$ cat go.mod
module http-status-checker

go 1.18

$ docker compose up -d

$ docker compose ps
NAME                    COMMAND                  SERVICE               STATUS              PORTS
hogehoge.uzimihsr.com   "/docker-entrypoint.…"   nginx                 running             80/tcp, 0.0.0.0:443->443/tcp
http-status-checker     "./app https://hogeh…"   http-status-checker   exited (1)

$ docker compose logs http-status-checker
http-status-checker  | 2022/09/28 12:17:41 target: https://hogehoge.uzimihsr.com/, insecure: false
http-status-checker  | 2022/09/28 12:17:41 [ERROR] HTTP(S) request failed: Get "https://hogehoge.uzimihsr.com/": x509: certificate signed by unknown authority

Trust the certificate in a scratch image

To avoid the certificate error, the self-signed certificate should be trusted in a scratch image.
(Of course, it is possible to set tls.Config.InsecureSkipVerify as a workaround, but I have tried to trust the self-signed certificate.)

Since the golang image is Debian-based, the list of CA certificates can be updated with the update-ca-certificates command.

The Dockerfile is rewrited as follows.

The following operations are added: copy the certificate we want to trust with .crt extension and run the update-ca-certificates command.
This allows /etc/ssl/certs/ca-certificates.crt in the build stage to trust the specified certificate.

COPY server-cert.pem /usr/local/share/ca-certificates/server-cert.crt
RUN update-ca-certificates

Finally, rebuild and restart the container.
This time it worked fine with no certificate errors.🎉

$ docker compose up -d --remove-orphans --build

$ docker compose ps
NAME                    COMMAND                  SERVICE               STATUS              PORTS
hogehoge.uzimihsr.com   "/docker-entrypoint.…"   nginx                 running             80/tcp, 0.0.0.0:443->443/tcp
http-status-checker     "./app https://hogeh…"   http-status-checker   exited (0)

$ docker compose logs http-status-checker
http-status-checker  | 2022/09/28 13:44:43 target: https://hogehoge.uzimihsr.com/, insecure: false
http-status-checker  | 2022/09/28 13:44:43 status: 200 OK