Serving CA certificates on the cheap
Introduction
This is a third post in the High Altitude Water Aerosols series with the aim to get “cloud native” work for internal (web-)services.
We already have a container service running on Alpine and a somewhat reasonable reverse proxy config.
Problem statement
Previously I also explained how to run one’s own root Certificate Authority, but without a web server publishing the CA certificate(s) to the internal net.
Obviously running some kind of a behemoth for an extremely low-traffic endpoint is… dumb.
Let’s do the smart (resource-wise) thing, then.
Road to solution
While initially researching this topic, I came across Florin Lipan’s article titled The smallest Docker image to serve static websites. And indeed, a binary weighing a few KB is exactly the kind of thing I’m looking for.
So with a few tweaks, this stuff should be easy. For realsies this time.
Solution
To get an appropriate Docker image, I expropriated and customized Florian’s original template, bumping version as I went along:
# Taken from https://lipanski.com/posts/smallest-docker-image-static-website
FROM alpine:3.18 AS builder
# Install all dependencies required for compiling busybox
RUN apk add gcc musl-dev make perl
# Download busybox sources
RUN wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 \
&& tar xf busybox-1.36.1.tar.bz2 \
&& mv /busybox-1.36.1 /busybox
WORKDIR /busybox
# Copy the busybox build config (limited to httpd)
COPY busybox.config ./.config
# Compile and install busybox
RUN make && make install
# Create a non-root user to own the files and run our server
RUN adduser -D static
# Switch to the scratch image
FROM scratch
EXPOSE 3000
# Copy over the user
COPY --from=builder /etc/passwd /etc/passwd
# Copy the busybox static binary
COPY --from=builder /busybox/_install/bin/busybox /
# Use our non-root user
USER static
WORKDIR /home/static
# Customized httpd.conf
# see https://git.busybox.net/busybox/tree/networking/httpd.c for syntax
COPY httpd.conf .
# Copy the static website
COPY web web
# Run busybox httpd
CMD ["/busybox", "httpd", "-f", "-v", "-p", "3000", "-c", "httpd.conf"]
Obviously of interest are the busybox.config
, httpd.conf
and web
subdirs.
The BusyBox config
The delta between my final busybox.config
and config from make allnoconfig
is as follows:
diff -U0 .config busybox.config
--- .config 2024-01-15 18:44:57.107966359 +0100
+++ busybox.config 2023-09-09 20:12:34.000000000 +0200
@@ -4 +4 @@
-# Mon Jan 15 18:44:57 2024
+# Sat Sep 9 20:10:01 2023
@@ -29 +29 @@
-# CONFIG_INSTALL_NO_USR is not set
+CONFIG_INSTALL_NO_USR=y
@@ -43 +43 @@
-# CONFIG_STATIC is not set
+CONFIG_STATIC=y
@@ -129 +129 @@
-# CONFIG_LOOP_CONFIGURE is not set
+CONFIG_LOOP_CONFIGURE=y
@@ -131 +131 @@
-CONFIG_TRY_LOOP_CONFIGURE=y
+# CONFIG_TRY_LOOP_CONFIGURE is not set
@@ -863,2 +863,2 @@
-# CONFIG_HTTPD is not set
-CONFIG_FEATURE_HTTPD_PORT_DEFAULT=0
+CONFIG_HTTPD=y
+CONFIG_FEATURE_HTTPD_PORT_DEFAULT=80
@@ -867 +867 @@
-# CONFIG_FEATURE_HTTPD_BASIC_AUTH is not set
+CONFIG_FEATURE_HTTPD_BASIC_AUTH=n
@@ -873,7 +873,7 @@
-# CONFIG_FEATURE_HTTPD_ERROR_PAGES is not set
-# CONFIG_FEATURE_HTTPD_PROXY is not set
-# CONFIG_FEATURE_HTTPD_GZIP is not set
-# CONFIG_FEATURE_HTTPD_ETAG is not set
-# CONFIG_FEATURE_HTTPD_LAST_MODIFIED is not set
-# CONFIG_FEATURE_HTTPD_DATE is not set
-# CONFIG_FEATURE_HTTPD_ACL_IP is not set
+CONFIG_FEATURE_HTTPD_ERROR_PAGES=y
+CONFIG_FEATURE_HTTPD_PROXY=y
+CONFIG_FEATURE_HTTPD_GZIP=n
+CONFIG_FEATURE_HTTPD_ETAG=y
+CONFIG_FEATURE_HTTPD_LAST_MODIFIED=y
+CONFIG_FEATURE_HTTPD_DATE=y
+CONFIG_FEATURE_HTTPD_ACL_IP=n
@@ -1126 +1126 @@
-# CONFIG_ASH_SLEEP is not set
+CONFIG_ASH_SLEEP=y
Not a lot of features needed, eh? I’m sure it could be trimmed further.
Btw, diff against Florin’s .config
ended up:
diff -U0 .config busybox.config
--- .config 2024-01-15 18:49:01.146905810 +0100
+++ busybox.config 2023-09-09 20:12:34.000000000 +0200
@@ -4 +4 @@
-# Mon Nov 6 11:52:15 2023
+# Sat Sep 9 20:10:01 2023
@@ -62 +62 @@
-# CONFIG_INSTALL_APPLET_SYMLINKS is not set
+CONFIG_INSTALL_APPLET_SYMLINKS=y
@@ -867 +867 @@
-CONFIG_FEATURE_HTTPD_BASIC_AUTH=y
+CONFIG_FEATURE_HTTPD_BASIC_AUTH=n
@@ -875 +875 @@
-CONFIG_FEATURE_HTTPD_GZIP=y
+CONFIG_FEATURE_HTTPD_GZIP=n
@@ -879 +879 @@
-CONFIG_FEATURE_HTTPD_ACL_IP=y
+CONFIG_FEATURE_HTTPD_ACL_IP=n
@@ -1126 +1126 @@
-# CONFIG_ASH_SLEEP is not set
+CONFIG_ASH_SLEEP=y
The httpd config
Next up is the heart of the “serving stack”1, the httpd.conf
:
H:/home/static/web
I:index.html
E404:404.txt
.crt:application/x-x509-ca-cert
I think the only interesting part there is the .crt:...
line that
sets the correct mime-type application/x-x509-ca-cert
so the browsers
know what’s up when you point them on the .crt
file.
The web subdirectory
Oh goodness, this one is super complicated :-D
# Copy the cert from somewhere
ruby -pe 'next unless /BEGIN/../END/' /path/to/ca-cert.crt > ca.crt
# Create fancy 404 page
echo "That ain't here." > 404.txt
# Create fancy index.html page
cat - > index.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wejn's Awesome Certification Authority</title>
</head>
<body>
<main>
<h1>Wejn's Awesome Certification Authority</h1>
<p>You might be looking for <a href="ca.crt">the certificate</a>.</p>
</main>
</body>
</html>
EOF
Composer stanza
With all that stuff in an appropriate places:
$ ls -1
ca
compose.yml
sp
traefik
$ find ca
ca
ca/web
ca/web/404.txt
ca/web/ca.crt
ca/web/index.html
ca/httpd.conf
ca/Dockerfile
ca/busybox.config
all that’s left is the compose.yml
stanza that spins this badboy up:
services:
sp:
# [... see previous post(s) ...]
traefik:
# [... see previous post(s) ...]
ca:
build: ca
stop_signal: SIGKILL
labels:
- "traefik.enable=true"
restart: always
Notice that the ca
service re-uses the default traefik config that
just spins it up as ca.int.wejn.org
on both HTTP and HTTPs (no redirects).
Stats
The image ends up being ridiculous 173 kB
(that’s all in, assets and all):
$ # podman images |grep _ca
localhost/int_ca latest 51d2b095d782 4 months ago 173 kB
And runtime is hardly any bigger:
ps -o vsz,rss,comm,args | grep [b]usy
324 120 busybox /busybox httpd -f -v -p 3000 -c httpd.conf
So, yeah, I’d say 324 kB VSZ and 120 kB RSS is agréable. For a mostly idle (but nice to have around) service2.
Closing words
See, I said this one was easy, mostly due to the groundwork laid down by the previous posts.
I think I have one more post to wrap this series – one where I go “zero to hero” with some brand new internal (web-)service.