Tue, 12 Jan 2021 17:00:00 +0100
container, docker, netbox, software, sysadmin

Netbox Docker mit SAML2-Plugin

Aus verschiedenen Gründen will ich das Netbox Plugin for SSO using SAML2 in einer Docker-Umgebung ausprobieren. Die Installation ist nicht ganz trivial, darum schreibe ich meine Erlebnisse hier auf.

Die Beschreibung im Netbox Docker Wiki weist den Weg, wie man am schnellsten zum Ziel kommt.. Zunächst heißt es, das Repository zu klonen und in das Verzeichnis zu wechseln:

git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker

Anschließend sind einige Dateien anzupassen beziehungsweise neu anzulegen.

Bei Netbox Docker wird sehr viel über Umgebungsvariablen konfiguriert, die ich in der Datei env/netbox.env anpassen kann. Wichtig für das Plugin sind die folgenden Variablen:

REMOTE_AUTH_ENABLED=True
REMOTE_AUTH_BACKEND=netbox.authentication.RemoteUserBackend
REMOTE_AUTH_AUTO_CREATE_USER=True

In der Beschreibung des Plugins steht bei REMOTE_AUTH_BACKEND noch utilities.auth_backends.RemoteUserBackend. Seit Netbox-Version 2.9 lautet der richtige Wert jedoch netbox.authentication.RemoteUserBackend.

In der Datei configuration/configuration.py wird das Plugin aktiviert und konfiguriert. Am Ende der Datei habe ich erstmal nur die Änderungen eingetragen, die im README zum Plugin vorgeschlagen werden.

PLUGINS = ['django3_saml2_nbplugin']

PLUGINS_CONFIG = {
    'django3_saml2_nbplugin': {

        # Use the Netbox default remote backend
        'AUTHENTICATION_BACKEND': REMOTE_AUTH_BACKEND,

        # Custom URL to validate incoming SAML requests against
        'ASSERTION_URL': 'https://netbox.company.com',

        # Populates the Issuer element in authn reques e.g defined as "Audience URI (SP Entity ID)" in SSO
        'ENTITY_ID': 'https://netbox.conpany.com/',

        # Metadata is required, choose either remote url or local file path
        'METADATA_AUTO_CONF_URL': "https://mycorp.okta.com/app/sadjfalkdsflkads/sso/saml/metadata"
    # or local file path
        #'METADATA_LOCAL_FILE_PATH': '/opt/netbox/saml2.xml',
    }
}

Bei AUTHENTICATION_BACKEND wird auf die Variable REMOTE_AUTH_BACKEND Bezug genommen, darum ist es wichtig, dass die Plugin-Konfiguration nach dieser Variablen in der Datei configurations.py steht.

Die Variablen ASSERTION_URL, ENTITY_ID und METADATA_AUTO_CONF_URL bestimmen die Kommunikation mit dem SAML2-Identity-Provider und müssen daher in Abstimmung mit diesem konfiguriert werden. Dabei ist METADATA_AUTO_CONF_URL eine URL, mit der das Plugin sich beim Start von Netbox die Metadaten für die Authentisierung als XML-Datei abholt. Alternativ dazu ist es möglich, diese XML-Datei selbst abzuholen, im Container zu platzieren und dann in der Variable METADATA_LOCAL_FILE_PATH darauf zu verweisen.

METADATA_LOCAL_FILE_PATH und METADATA_AUTO_CONF_URL schließen einander aus, es sollte nur eine der beiden Varianten verwendet werden.

In die Datei plugin_requirements.txt werden die benötigten Python-Module eingetragen:

netbox-plugin-auth-saml2
django3-auth-saml2

Die Datei docker-compose.override.yml wird auch ohne Plugins benötigt. Da ich die Images für netbox und netbox-worker neu bauen muss, sieht die Datei hier wie folgt aus:

version: '3.4'
services:
  nginx:
    ports:
      - 8000:8080
    volumes:
      - ./netbox-nginx.conf:/etc/netbox-nginx/nginx.conf:ro
  netbox:
    build:
      context: .
      dockerfile: Dockerfile-Plugins
    image: netbox:v2.9-plugins
  netbox-worker:
    image: netbox:v2.9-plugins

Zusätzlich zur einfachen docker-compose.override.yml, werden hier die Container netbox und netbox-worker neu gebaut, um die benötigten Python-Module zu installieren. Außerdem bekommt nginx eine angepasste Konfigurationsdatei, weil beim SAML2-Plugin ein paar Pfade beim Zugriff auf netbox umgelenkt werden müssen.

Die Datei Dockerfile-Plugins ist als dockerfile für den Bau der Container angegeben. Diese sieht so aus:

FROM netboxcommunity/netbox:v2.9

RUN apk add --no-cache xmlsec

copy ./plugin_requirements.txt /
RUN pip install  --no-warn-script-location -r /plugin_requirements.txt

# see https://github.com/sabnzbd/sabnzbd/issues/1451
# python3.9 gives "AttributeError: module 'base64' has no attribute 'decodestring'"
#
# fixed in pysaml2=5.1.0
#
#RUN sed -i 's/base64.decodestring/base64.decodebytes/g' /usr/local/lib/python3.9/site-packages/saml2/saml.py
#RUN sed -i 's/base64.encodestring/base64.encodebytes/g' /usr/local/lib/python3.9/site-packages/saml2/saml.py

Das SAML2-Plugin benötigt zusätzlich das Binärprogramm xmlsec1, dass ich durch Installieren des Software-Pakets xmlsec mit apk bekomme. Danach werden die zusätzlichen Python-Module installiert.

Die auskommentierten Zeilen können je nach verwendeter Version von Netbox (hier v2.9) nötig sein oder nicht. In Version 3.9 von Python sind die Methoden decodestring und encodestring aus dem Modul base64 entfernt worden. Das Modul pysaml2, welches automatisch geladen wird, verwendet in Version 5.0.0 noch die alten Methoden und ab Version 5.1.0 nicht mehr. Sollten sich kurz nach dem Start von Netbox Docker die Container netbox und/oder netbox-worker mit der Fehlermeldung AttributeError: module 'base64' has no attribute 'decodestring' beenden, aktiviert man am einfachsten die beiden sed Befehle.

Mit den folgenden Befehlen werden die Images gebaut und anschließend gestartet:

sudo docker-compose build --no-cache
sudo docker-compose up -d

Laut docker-compose.override.yml sollte die Netbox nun an Port 8000 des Testrechners erreichbar sein.

Unmittelbar nach dem Start einer frischen Netbox wird zunächst die Datenbank initialisiert. In dieser Zeit werden keine Anfragen beantwortet und im Browser bekommt man die Meldung Bad GAteway.

Dauert dieser Zustand länger an, kann man mit folgendem Befehl nachschauen, ob alle Container laufen:

sudo docker-compose ps

Hat sich ein Container vorzeitig beendet, sieht die Ausgabe etwa so aus:

            Name                           Command               State                            Ports
--------------------------------------------------------------------------------------------------------------------------------
netbox-docker_netbox-worker_1   python3 /opt/netbox/netbox ...   Exit 1
netbox-docker_netbox_1          /opt/netbox/docker-entrypo ...   Up
netbox-docker_nginx_1           /docker-entrypoint.sh ngin ...   Up       80/tcp, 0.0.0.0:8000->8080/tcp,0.0.0.0:32773->8080/tcp
netbox-docker_postgres_1        docker-entrypoint.sh postgres    Up       5432/tcp
netbox-docker_redis-cache_1     docker-entrypoint.sh sh -c ...   Up       6379/tcp
netbox-docker_redis_1           docker-entrypoint.sh sh -c ...   Up       6379/tcp

In diesem Fall hilft oft, die Logs anzuschauen, um zu sehen, wie man das Problem beheben kann:

sudo docker-compose logs netbox-worker|less -r

Die Ausgabe hier zeigt, dass manage.py sich mit einer Fehlermeldung beendet hatte:

Attaching to netbox-docker_netbox-worker_1
netbox-worker_1  | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox-worker_1  | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox-worker_1  | 🧬 loaded config '/etc/netbox/config/extra.py'
netbox-worker_1  | Traceback (most recent call last):
netbox-worker_1  |   File "/opt/netbox/netbox/manage.py", line 10, in <module>
netbox-worker_1  |     execute_from_command_line(sys.argv)
...
netbox-worker_1  |   File "/usr/local/lib/python3.9/site-packages/saml2/saml.py", line 90, in <module>
netbox-worker_1  |     _b64_decode_fn = getattr(base64, 'decodebytes', base64.decodestring)
netbox-worker_1  | AttributeError: module 'base64' has no attribute 'decodestring'

In diesem Fall sind die beiden sed Befehle nötig, die oben am Ende der Datei Dockerfile-Plugins stehen.

Ist das Problem behoben und Netbox Docker einsatzbereit, kann man sich dem Test der Authentisierung mit SAML2 widmen.

Für den produktiven Einsatz muss auf jeden Fall noch ein Container für die Verschlüsselung des HTTP-Datenverkehrs vorgeschaltet werden. Das ist ein anderes Thema.