Setup a LAN container registry with Podman and self-signed certs

󰃭 2023-02-14

Prerequisites

  • RHEL-compatible or Fedora-based Linux distribution
  • a LAN (presumably with access to other machines on the LAN)
  • Podman
  • OpenSSL

Install the required packages:

sudo dnf install '@container-management' openssl-devel openssl

Self-signed certificate

The benefit of having at least a self-signed certificate is that you can encrypt the traffic to your container registry over your LAN. You know, in the event there is an unknown entity hiding in your house or office, snooping on your local HTTP traffic. A self-signed certificate is fine for LAN-wide access, because presumably you trust yourself; however, if you want something that is accessible from the public Internet then you’d want a certificate signed by a Certificate Authority, because other people on the public Internet using it don’t know if they should trust you to encrypt their HTTP traffic.

Create directories to hold the self-signed certificate and htpasswd authorization.

mkdir -p ~/registry/{auth,certs}

Create a subjectAltName configuration file (san.cnf). This will contain information and configure some settings about the self-signed certificate. The information I have in the [req_distinguished_name] and [alt_names] sections are for an example; you should enter information that is relevant to your use-case.

Change into the ~/registry/certs directory and create a file named san.cnf with the following contents:

[req]
default_bits = 4096
distinguished_name = req_distinguished_name
req_extensions = req_ext
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
countryName = US
stateOrProvinceName = Illinois
localityName = Chicago
commonName = 127.0.0.1: Self-signed certificate

[req_ext]
subjectAltName = @alt_names

[v3_req]
subjectAltName = @alt_names

[alt_names]
IP.1 = 10.0.0.128
DNS = something.local

Make sure to change the IP.1 and DNS values to the LAN IP address and hostname of your registry server machine.

Make sure you’re in the ~/registry/certs directory. Now generate the key:

openssl req -new -nodes -sha256 -keyout something.local.key -x509 -days 365 -out something.local.crt --config san.cnf

htpasswd authentication

Now we need generate an htpasswd file so that you can authenticate to the registry server. We’ll use the registry:2.7.0 container image with an entrypoint to do this. First, change into the auth subdirectory.

Note: you must use the 2.7.0 tag for the registry image, because it seems to be the only one that has the htpasswd command available for the entrypoint flag.

cd ../auth
podman run --rm --entrypoint htpasswd registry:2.7.0 -Bbn USERNAME PASSWORD > htpasswd

The USERNAME and PASSWORD you choose will be used with the podman login command to authenticate you to the registry server.

Deploy the registry server

Create a directory on the host to store the registry data:

sudo mkdir -p /var/lib/registry

To deploy the registry server, run:

Note: The port mapping of 443:443 is for if you have no other web server running on the machine running the registry server. If you do have another web server, then change the port mapping to 5000:5000.

sudo podman run \
    --privileged \
    -d \
    --name registry \
    -p 443:443 \
    -v /var/lib/registry:/var/lib/registry:Z \
    -v "$HOME/registry/auth:/auth:Z" \
    -v "$HOME/registry/certs:/certs:Z" \
    -e REGISTRY_AUTH=htpasswd \
    -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
    -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
    -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
    -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/something.local.crt \
    -e REGISTRY_HTTP_TLS_KEY=/certs/something.local.key \
    registry:latest

The registry container should run without incident. You can run sudo podman logs registry and sudo podman ps to ensure everything is as it should be.

If you get an error that tells you that 443 is a privileged port, you can choose a port higher than 1024, or you can add the following line to /etc/sysctl.conf:

/etc/sysctl.conf

net.ipv4.ip_unprivileged_port_start=443

Then, to load the new value from /etc/sysctl.conf, run:

sudo sysctl -p /etc/sysctl.conf

Accessing the registry from another machine

Since we’re using a self-signed certificate, we’re going to be our own Certificate Authority (CA). We can use ~/registry/certs/something.local.crt as our CA root certificate. So we’ll need to copy the contents of that file to the clipboard or copy the entire file to the machine from which you want to access the registry.

Run these commands on the other machine

Copy something.local.crt via SSH:

scp -v youruser@something.local:/home/youruser/registry/certs/something.local.crt .

Now we need to create a directory to store the CA in a place where the Docker daemon or Podman will look for it.

sudo mkdir -p /etc/containers/certs.d/something.local:443

If you’re running Docker on the other machine, then change /etc/containers to /etc/docker. If you’re using a port other than 443, then make sure to use that port in the name of the CA directory.

Now copy or move the CA file to the newly created directory, and make sure the resulting filename is ca.crt:

sudo mv something.local.crt /etc/containers/certs.d/something.local:443/ca.crt

Now you can try to login to the registry with the USERNAME and PASSWORD you created earlier:

podman login -u USERNAME -p PASSWORD something.local

If this works, you should see “Login succeeded!” printed to the console. You can now push and pull images to and from your self-hosted container image registry.

OPTIONAL: Using the container registry for ostree images

If you’re running an immutable ostree version of Fedora such as Silverblue or Kinoite, you can use your self-hosted registry to store customized container images to rebase to. Just make sure that the registry is on a LAN server machine that is always up and running. To rebase from a container image in your registry, we have to make sure that rpm-ostree knows you’re authenticated to the registry. Run the podman login command with the following flags:

podman login -u USERNAME -p PASSWORD something.local -v

The -v flag is especially important, as it will show the name of the auth.json file we need that contains the authentication info. The file should be /run/user/1000/containers/auth.json. We simply need to copy that file to /etc/ostree.

sudo cp -v /run/user/1000/containers/auth.json /etc/ostree/

Now rpm-ostree knows you’re authenticated. Assuming you’ve built and pushed your custom ostree container image to your self-hosted registry, you can rebase to it with the following command:

rpm-ostree rebase --experimental ostree-unverified-registry:something.local/custom_silverblue:latest

Enter your instance's address