I needed a quick and dirty way to retrieve import a ca certificate from my docker registry so I don’t have to use the insecure_registries option in my /etc/docker/daemon.json file. There were several ways to go about this and here they are.
This option works, except it adds an extra character to the end of the last line. I couldn’t figure out how to strip these off. I think it is some null character or something.
mkdir -p /etc/docker/certs.d/registry.example.com; echo | openssl s_client -showcerts -servername registry.example.com -connect registry.example.com:443 2>&1 | grep -zoE '\-\-\-\-\-BEGIN CERTIFICATE-----.*END CERTIFICATE-----' > /etc/docker/certs.d/registry.example.com/ca.crt
Breaking down this one line command give us the first part, which is to ensure the certs.d directory already exists. Using a -p with mkdir will make sure the parent directory to the domain name directory exists. The certs.d directory is not normally there by default.
We then an empty echo to openssl. This is because the openssl command will connect to the server and wait for input from the user to send to it. We don’t really want to send anything for this.
Next, the openssl command starts up it’s client and will use the servername option to connect to the destination server on the destination port. We need to specify the servername option because multiple domain names can be hosted on a destination web server. This tells the server which one we want. The connect option is the actual server we are connecting to. We also specify 443 as the port, the default for https. The 2>&1 redirects standard error to standard input to make the output not show any errors. This part can be omitted.
The following command, grep, is how we search for and only pull out the part we want. There are several options we need for it to work. By default, grep works line by line and we need the regex to match over multiple lines so we use the -z option (a data line ends in 0 byte, not newline). The -o option makes grep only show what we are searching for. Finally, the -E treats the string following as an extended regular expression.
The Last part is the saving of the certificate to the output file. A single > will overwrite anything already existing.
Using Grep and Python
Being a Python fan, I took the above openssl command and piped it to python instead of grep. I then used python’s regular expression search method.
mkdir -p /etc/docker/certs.d/registry.example.com; echo | openssl s_client -showcerts -servername registry.example.com -connect registry.example.com:443 2>&1 | grep -zoE '\-\-\-\-\-BEGIN CERTIFICATE-----.*END CERTIFICATE-----' | python -c "exec('import sys\nfor l in sys.stdin:\n print(l.strip())')" > /etc/docker/certs.d/registry.example.com/ca.crt
The python portion of the command uses exec to run the following python code. This is needed because there are problems with running a for loop following an import statement in a one-liner python command. The for loop iterates over each line of piped standard input and prints out a white space striped line. This is what removes the extra character from the last line in the grep portion
Using only Python
Another way to do it is to us the python regular expression library and not use grep. This is what this example does for us.
mkdir -p /etc/docker/certs.d/registry.example.com; echo | openssl s_client -showcerts -servername registry.example.com -connect registry.example.com:443 2>&1 | python -c 'import re, sys; print(re.search("(-----BEGIN CERTIFICATE-----.*END CERTIFICATE-----)", sys.stdin.read(), re.DOTALL).groups())' > /etc/docker/certs.d/registry.example.com/ca.crt
I like this last option better because it uses fewer commands and doesn’t use a for loop. It starts by taking the input, searches for the regex, and prints it out. The re.DOTALL allows the search to go over multiple lines and the parenthesis in the regex is out a group is assigned to the match. There should only be one group, so the list index is 0.