In dem Beitrag über Alma Linux habe ich ja beschrieben wie man einen Secure Socket Funneling Server aufsetzt. Jetzt beschreibe ich wie man den dazugehörigen Client erstellt.
Damit der Client nicht manuell installiert werden muss packe ich ihn in einen Container. Die Idee ist genial und überzeugend. Man packt alle notwendigen Artefakte die der Client zur Ausführung braucht in einen Container. Ist der Container gebaut, kann man ihn überall hin transportieren und starten wo eine Container Runtime zur Verfügung steht.
Der Container, oder genauer gesagt das Container Image wird i.d.R. auf eine Registry hochgeladen (push) und dann auf dem Zielsystem wieder heruntergeladen (pull). Ich zeige hier mal einen Weg wie man das Container Image als Datei speichert und dann von Hand auf das Zielsystem kopiert. Wieso einfach, wenn es auch kompliziert geht ;-)
Zum Erstellen eines Container Images benötigt man Docker bzw. Podman und ein sogenanntes Dockerfile.
FROM debian:buster-slim
# expose server port and node port
EXPOSE 8011 32000
# set workdir to /app
WORKDIR /app
# environment variables
ENV MODE client
ARG HOST
ENV HOST $HOST
ENV PORT 8011
ARG FORWARD_HOST
ENV FORWARD_HOST $FORWARD_HOST
ENV FORWARD_SERVICE 32000
# create folder for certs and symbolic links
RUN mkdir -p /app/certs/trusted && \
ln -s /certs/ca.crt /app/certs/trusted/ca.crt && \
ln -s /certs/dh4096.pem /app/certs/dh4096.pem && \
ln -s /certs/certificate.crt /app/certs/certificate.crt && \
ln -s /certs/private.key /app/certs/private.key
# copy all needed files into container image
COPY ssf ssfd start.sh /app
# start command
CMD ["/app/start.sh"]
Zuerst muss man sich überlegen, was alles in das Container Image rein soll. Dann wählt man ein Basis Image aus was möglichst schlank ist aber alles bereitstellt, was für den Client benötigt wird. Ich habe hier mal debian:buster-slim genommen.
Dann lege ich fest welche Ports standardmäßig veröffentlicht werden sollen. Hier Port 8011 für den ssf Server und Port 32000 als Node Port für den Backend Service. Wie gesagt das sind Standard Ports die man später zur Laufzeit noch umbiegen kann.
Als Arbeitsverzeichnis verwende ich /app. Es ist einfach strukturierter wenn man seine Dateien in einem Arbeitsverzeichnis bündelt.
Die Umgebungsvariabeln setzen dann alle Werte die man später benötigt. Grundsätzlich verwendet man immer Variablen für Werte die veränderlich sind ;-)
Mit Run werden dann im Container symbolische Links für die Zertifikate angelegt. Über diese Links greift dann später ssf bzw. ssfd auf die Zertifikate zu. Ich kann ja nicht selbst die Zertifikate in den Container kopieren weil das sensible Daten sind. Die Zertifikate werden zur Laufzeit über z.B. ein Kubernetes Secret eingebunden.
Copy kopiert dann noch die executables ssf, ssfd und start.sh in den Container.
Als Command habe ich ein Start Skript hinterlegt, das natürlich auch in den Container rein muss. So ein Start Skript ist flexibler und man kann noch ein bisschen Logik einbauen, z.B. den Loglevel setzen oder steuern ob der Container im Client oder Server Modus gestartet werden soll.
#!/bin/bash
# loglevel critical|error|warning|info|debug|trace (default: info)
if [[ -z ${LOGLEVEL} ]] ; then
export LOGLEVEL=info
else
set -x
echo "LOGLEVEL=${LOGLEVEL}"
fi
# check environment variables
if [[ -z ${MODE} ]] ; then
echo "MODE not set!"
exit 1
else
if [[ ${MODE} == server || ${MODE} == client ]] ; then
echo "MODE=${MODE}"
else
echo "MODE must contain server or client!"
exit 1
fi
if [[ ${MODE} == client && -z ${FORWARD_HOST} ]] ; then
echo "FORWARD_HOST not set!"
exit 1
else
echo "FORWARD_HOST=${FORWARD_HOST}"
fi
if [[ ${MODE} == client && -z ${FORWARD_PORT} ]] ; then
echo "FORWARD_PORT not set!"
exit 1
else
echo "FORWARD_PORT=${FORWARD_PORT}"
fi
if [[ ${MODE} == client && -z ${HOST} ]] ; then
echo "HOST not set!"
exit 1
else
echo "HOST=${HOST}"
fi
fi
if [[ -z ${PORT} ]] ; then
echo "PORT not set!"
exit 1
else
echo "PORT=${PORT}"
fi
# start ssf server or client
cd /app
ls /app/certs
ls /app/certs/trusted
if [[ ${MODE} == server ]] ; then
/app/ssfd -v ${LOGLEVEL} -p ${PORT} -g
else
/app/ssf -v ${LOGLEVEL} -p ${PORT} -R ${FORWARD_PORT}:${FORWARD_HOST}:${FORWARD_PORT} ${HOST}
fi
Ok Let's go Hase. Jetzt kann das Container Image gebaut werden.
Wenn man seinen HOST und den FORWARD_HOST schon kennt, kann man diese Werte setzen.
export HOST=""
export FORWARD_HOST=""
podman build --build-arg HOST=$HOST --build-arg FORWARD_HOST=$FORWARD_HOST -t localhost/ssf:3.0.0 .
podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/ssf 3.0.0 fb2e07c2a585 12 days ago 98.2 MB quay.io/podman/hello latest 804953f8be6e 2 weeks ago 82.3 kB docker.io/library/debian buster-slim e1635ddc0b1d 2 weeks ago 72.5 MB
Wenn das Container Image gebaut ist kann man es ganz einfach mit diesem Befehl speichern.
podman image save -o ssf.tar localhost/ssf:3.0.0
ls -la ssf.tar
-rw-r--r-- 1 root root 98226688 Mar 5 12:43 ssf.tar
Das gespeicherte Container Image "ssf.tar" muss dann auf den Single Node Cluster kopiert werden. Das kann z.B. mit scp erfolgen.
Auf dem Single Node Cluster kann man dann das Image in die Container Runtime containerd "laden".
Dazu gibt es für containerd die die tools "crictl" und "ctr"
# ctr -n=k8s.io image import ssf.tar
TODO FIX: crictl images WARN und ERROR --> https://www.fatlan.com/08-08-2022-crictl-uyarisi-cozum/
# crictl images WARN[0000] image connect using default endpoints: [unix:///var/run/dockershim.sock unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]. As the default settings are now deprecated, you should set the endpoint instead. ERRO[0000] validate service connection: validate CRI v1 image API for endpoint "unix:///var/run/dockershim.sock": rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial unix /var/run/dockershim.sock: connect: no such file or directory" IMAGE TAG IMAGE ID SIZE docker.io/flannel/flannel-cni-plugin v1.1.2 7a2dcab94698c 3.84MB docker.io/flannel/flannel v0.21.5 a6c0cb5dbd211 26.3MB docker.io/openebs/node-disk-exporter 2.1.0 5ab2c114fe2e9 49.2MB docker.io/openebs/node-disk-manager 2.1.0 297e6f2690c43 50.9MB docker.io/openebs/node-disk-operator 2.1.0 f9669ba5fa2b8 49.2MB docker.io/openebs/provisioner-localpv 3.4.0 382df876b172f 28.8MB quay.io/jetstack/cert-manager-cainjector v1.11.2 7eaaec69ab5cf 12.6MB quay.io/jetstack/cert-manager-controller v1.11.2 ddd138f621ece 18.4MB quay.io/jetstack/cert-manager-ctl v1.11.2 35320f786aaf5 16.7MB quay.io/jetstack/cert-manager-webhook v1.11.2 342b00843ed6d 14.3MB registry.k8s.io/coredns/coredns v1.9.3 5185b96f0becf 14.8MB registry.k8s.io/etcd 3.5.6-0 fce326961ae2d 103MB registry.k8s.io/ingress-nginx/controller <none> 2db0b57c87122 117MB registry.k8s.io/ingress-nginx/kube-webhook-certgen <none> 5a86b03a88d23 20.1MB localhost/ssf 3.0.0 fb2e07c2a585a 98.2MB registry.k8s.io/kube-apiserver v1.26.4 35acdf74569d8 35.5MB registry.k8s.io/kube-controller-manager v1.26.4 ab525045d05c7 32.4MB registry.k8s.io/kube-proxy v1.26.4 b19f8eada6a93 21.5MB registry.k8s.io/kube-scheduler v1.26.4 15a59f71e969c 17.7MB registry.k8s.io/pause 3.8 4873874c08efc 311kB registry.k8s.io/pause 3.9 e6f1816883972 322kB
Es ist zwar etwas umständlich Container Images ohne eine Registry zu übertragen, aber man lernt dafür wie es funktioniert und was dabei zu beachten ist. Außerdem kann man dieses Vorgehen für Container Images verwenden, welche man nicht auf einer Registry veröffentlichen möchte.