IPAM on a Budget

Bei meinen Reisen als Consultant/Architekt durch die verschiedensten Kunden erlebe ich häufig eins: Große Excel-Dateien für die Verwaltung von IP-Adressen.
Eines vorweg: dieser Blogeintrag wird nicht gesponsort oder ähnliches.

Das Problem dabei?

Konsistenz, verteiltes Arbeiten (zumindest das ist durch SharePoint besser geworden) und viele manuelle Schritte. Allein schon die „Beantragung“ von IP-Adressen ist oftmals mühsam.

Was also tun?

Wenn es geht empfehle ich immer den Einsatz einer professionellen IPAM/DDI Lösung wie Infoblox.
Wie aber der Titel schon verrät geht es hier um eine Budget-Lösung, also einer Lösung die günstig ausfällt. Denn das was Lösungen, wie die oben genannte, angeht, sind diese selten günstig (aus der Perspektive der Anschaffung) und auch selten schnell implementiert. Beides ist durchaus berechtigt, aber wie gesagt, darum geht es heute nicht.

Eine mögliche Lösung.

Ich selbst setze phpIPAM ein. Das ist eine auf php basierte IPAM-Lösung – DDI ist nicht enthalten.
Die Features leicht reichen um ein „IP-Excel“ abzulösen und gehen soweit, dass neben den reinen IPAM Features (IPv4/IPv6 Adressverwaltung, Subnetz-Verwaltung, Visualisierung von Subnetzen, DNS Integration, Scannen von Subnetzen, Rechteverwaltung auf Gruppen/Subnetze – auch per AD/LDAP/Raduis, etc.) auch z.B. ein Rack-Management dabei ist. Dokumentation der Racks einfach gemacht.
Auch spannend ist die Bereitstellung von REST APIs. Was bedeutet, dass sich die Lösung auch in ein bestehendes ITSM integrierten lässt.

Als Voraussetzung (Version 1.4) gilt:

  • Webserver: Apache2 oder Nginx
  • MySQL 5.1+ (Ja, MariaDB geht auch)
  • php: 5.4, 7.2+ (+ diverse Module und php PEAR)

Wie schon früher verwende ich für sowas gerne Container. So auch dieses Mal. Der für die Funktion selbst benötigte Container phpipam-www ist auf Docker Hub verfügbar und wird von phpIPAM selbst gepflegt.

Als cloudiger Mensch würde mein Design für eine Firma also im Grund wie folgt aussehen:

Zu den Komponenten

KomponenteFunktion
StorageSpeicherort für Konfigurationsdateien
phpipam-wwwAzure Container Instance zum Ausführen des php-Codes (ist ein privates ACI und somit Teil des vnets)
MySQL DatabasePaaS Dienst „MySQL“ – hier werden die eigentlichen Inhalte gespeichert
Company-vnetNetzwerk der Firma. Enthält die Azure Komponenten und idealerweise auch Verbindung zum (sofern vorhanden) On-Premises Netzwerk.
VMs/Server (blau / lila)Platzhalter für Cloud oder On-Premises Server/Systeme

Zur Kalulation – meine abrufbar unter https://azure.com/e/645daef7392a43b2a2737daaa4085842

Basis sind immer Listpreise in EUR und eine Laufzeit von 730 Stunden, sofern nicht anders angegeben. Region ist North Europe.

MySQL PaaS, Basic Tier, Gen 5, 1vCore~ 23 €
Storage Account, Std GP_v2, File Storage, 1 GB~ 0,10 €
ACI, Linux, 1 Container, 1vCPU, 1,5 GB Memory, 36000 Sekunden Laufzeit~ 0,40 €
Macht gesamt23,50 €
Azure Kalkulation

Die Kalkulation enthält nutzungsabhängige Bestandteile und kann somit naturgemäß je nach Nutzung zu einer abweichenden Abrechnung führen.
Und ja, wird der MySQL auch in einen Container verschoben, dann reduzieren sich die Laufzeitkosten erneut. Andererseits hatte ich jetzt auch keine Ambition mit hier um das Backup zu kümmern.

Azure Deployment

Die Azure Komponenten werden mit der Azure CLI erstellt. Hier der gesamte Block aus meiner Sample-Umgebung. Die CLI Statements müssen natürlich an die eigenen Bedürfnisse angepasst werden.

### Basic Settings ###

export LOCATION_NAME="northeurope"
export LOCATION_SHORT="eun"
export ENVIRONMENT_NAME="demo"
export PREFIX_NAME="tinyt"
export PROJECT_NAME="phpipam"
export TAGS="Project=phpIPAM"
export RESOURCE_GROUP="$PREFIX_NAME-$PROJECT_NAME-$LOCATION_SHORT-$ENVIRONMENT_NAME-rg"

export ACI_PERS_CONTAINER_NAME="phpipam-www-1-4"
export ACI_PERS_CONTAINER_REPO="phpipam/phpipam-www:latest"

##################################################################################################################################
# Step 0 - Login
az login

##################################################################################################################################
# Step 1 - Resource Group
az group create -l $LOCATION_NAME -n $RESOURCE_GROUP --tags $TAGS

##################################################################################################################################
# Step 2 - vnet
export VNET_NAME="$PROJECT_NAME-$LOCATION_SHORT-$ENVIRONMENT_NAME-vnet"
az network vnet create --resource-group $RESOURCE_GROUP --name $VNET_NAME --address-prefix 10.100.0.0/23 --tags $TAGS

##################################################################################################################################
# Step 3 - public ip
export PUBLIC_IP_NAME="$PROJECT_NAME-$LOCATION_SHORT-$ENVIRONMENT_NAME-pip"
az network public-ip create -g $RESOURCE_GROUP -n $PUBLIC_IP_NAME --allocation-method Static --location $LOCATION_NAME --tags $TAGS --sku Standard
export PUBLIC_IP_IP=$(az network public-ip show -g $RESOURCE_GROUP -n $PUBLIC_IP_NAME --query "{address: ipAddress}" -o tsv)

##################################################################################################################################
# Step 4 - natgw
export NAT_GW_NAME="$PROJECT_NAME-$LOCATION_SHORT-$ENVIRONMENT_NAME-natgw"
az network nat gateway create --resource-group $RESOURCE_GROUP --name $NAT_GW_NAME --location $LOCATION_NAME --public-ip-addresses $PUBLIC_IP_NAME --idle-timeout 4

# export NAT_GW_ID=$(az network nat gateway show --resource-group $RESOURCE_GROUP --name $NAT_GW_NAME --query "id" -o tsv)
# az tag create --resource-id $NAT_GW_ID --tags $TAGS

##################################################################################################################################
# Step 5 - subnet
export VNET_SUBNET_ACI_NAME="aci-eun-lab-sn"
az network vnet subnet create -n GatewaySubnet         --vnet-name $VNET_NAME -g $RESOURCE_GROUP --address-prefixes "10.100.0.0/27" --nat-gateway $NAT_GW_NAME
az network vnet subnet create -n $VNET_SUBNET_ACI_NAME --vnet-name $VNET_NAME -g $RESOURCE_GROUP --address-prefixes "10.100.0.32/27" --nat-gateway $NAT_GW_NAME --service-endpoints Microsoft.Storage
az network vnet subnet create -n compute-eun-lab-sn    --vnet-name $VNET_NAME -g $RESOURCE_GROUP --address-prefixes "10.100.0.64/27"

az network vnet subnet update -n $VNET_SUBNET_ACI_NAME --vnet-name $VNET_NAME -g $RESOURCE_GROUP

##################################################################################################################################
# Step 6 - storage account
export STORAGE_ACCOUNT_NAME_SUFFIX="containerconf"
export STORAGE_ACCOUNT_NAME="$PREFIX_NAME$LOCATION_SHORT$ENVIRONMENT_NAME$STORAGE_ACCOUNT_NAME_SUFFIX"
export STORAGE_ACCOUNT_SHARE_NAME="phpipam"

az storage account create -n $STORAGE_ACCOUNT_NAME -g $RESOURCE_GROUP -l $LOCATION_NAME --sku Standard_LRS --tags $TAGS --default-action Deny --https-only true --min-tls-version TLS1_2
az storage share create --account-name $STORAGE_ACCOUNT_NAME --name $STORAGE_ACCOUNT_SHARE_NAME --quota 1

az storage account network-rule add -g $RESOURCE_GROUP --account-name $STORAGE_ACCOUNT_NAME --vnet-name $VNET_NAME --subnet $VNET_SUBNET_ACI_NAME
az storage account network-rule add -g $RESOURCE_GROUP --account-name $STORAGE_ACCOUNT_NAME --ip-address $PUBLIC_IP_IP

export STORAGE_ACCOUNT_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP --account-name $STORAGE_ACCOUNT_NAME --query "[0].value" --output tsv)

##################################################################################################################################
# Step 7 - mysql (on-phpipam-eun-lab-mysql)
export MYSQL_ROOT_PASSWORD=$(date +%s | sha256sum | base64 | head -c 32 ; echo)
export MYSQL_SERVER_NAME="$PREFIX_NAME-$PROJECT_NAME-$LOCATION_SHORT-mysql"
export MYSQL_SERVER_USER="$PROJECT_NAME-dbadm"

az mysql server create -l $LOCATION_NAME -g $RESOURCE_GROUP -n $MYSQL_SERVER_NAME -u $MYSQL_SERVER_USER -p $MYSQL_ROOT_PASSWORD --sku-name B_Gen5_1 \
    --ssl-enforcement Disabled --minimal-tls-version TLS1_2 --public-network-access Enabled \
    --backup-retention 10 --geo-redundant-backup Disabled --storage-size 5120 --version 5.7 \
    --tags $TAGS

az mysql server firewall-rule create -g $RESOURCE_GROUP -s $MYSQL_SERVER_NAME -n $NAT_GW_NAME --start-ip-address $PUBLIC_IP_IP --end-ip-address $PUBLIC_IP_IP

az mysql db create -g $RESOURCE_GROUP -s $MYSQL_SERVER_NAME -n $PROJECT_NAME

##################################################################################################################################
# Step 8 - key vault
export KEYVAULT_NAME="$PREFIX_NAME-$PROJECT_NAME-$LOCATION_SHORT-keyv"

az keyvault create --location $LOCATION_NAME --name $KEYVAULT_NAME --resource-group $RESOURCE_GROUP --tags $TAGS

az keyvault secret set --name "100-$MYSQL_SERVER_NAME-User" --vault-name $KEYVAULT_NAME --description $MYSQL_SERVER_NAME --value $MYSQL_SERVER_USER
az keyvault secret set --name "100-$MYSQL_SERVER_NAME-Pass" --vault-name $KEYVAULT_NAME --description $MYSQL_SERVER_NAME --value $MYSQL_ROOT_PASSWORD

##################################################################################################################################
# Step 9 - aci
az container create \
    --resource-group $RESOURCE_GROUP \
    --name $ACI_PERS_CONTAINER_NAME \
    --image $ACI_PERS_CONTAINER_REPO \
    --ip-address Private \
    --vnet $VNET_NAME \
    --subnet $VNET_SUBNET_ACI_NAME \
    --location $LOCATION_NAME \
    --os-type Linux \
    --restart-policy Always \
    --ports 80 443 \
    --azure-file-volume-account-name $STORAGE_ACCOUNT_NAME \
    --azure-file-volume-account-key $STORAGE_ACCOUNT_KEY \
    --azure-file-volume-share-name $STORAGE_ACCOUNT_SHARE_NAME \
    --azure-file-volume-mount-path /phpipam/

Sind alle Deployments abgeschlossen, sind also folgende Azure Resourcen erstellt:

  • 1 Key Vault, in dem die Logins zu dem MySQL Server gespeichert sind
  • 1 MySQL Server und Datenbank
  • 1 Storage Account für die persistenten Daten
  • 1 NAT Gateway, für den sicheren Zugriff auf den Storage Account und den MySQL Server (ACL auf die Quell-IP Adresse)
  • 1 Public IP, für das NAT Gateway
  • 1 VNET, in dieses VNET ist der Container für’s phpIPAM eingebunden
  • 1 Azure Container Instance für das phpIPAM

Natürlich kann der Container auch mit einer Public IP und ohne VNET Join laufen, aber ich gehe doch davon aus, dass sensible Daten wie das IPAM nicht direkt öffentlich verfügbar sein sollen. In einer produktiven Umgebung ist sicherlich auch eine Firewall für die Azure Umgebung vorhanden; dann ist das NET Gateway und die Public IP natürlich hinfällig und die Variable PUBLIC_IP_IP wird einfach auf den Wert der Public IP der Firewall gesetzt.
Einen kleinen Hasenfuß hat diese Lösung allerdings. ACI unterstützt derzeit keine Global VNET Peering und ist folglich nur über die deployte Region (und alles was über vorher genannte Firewall läuft) zugreifbar.

Als nächstes wird die IP des ACI Containers in der Erfahrung gebracht. Dazu wird dieser einfach im Azure Portal aufgerufen und auf der Startseite ist auch schon die IP ersichtlich.

Beim Aufruf der IP-Adresse (in diesem Fall per http://10.100.0.36) erscheint eine 403er Fehlermeldung. Das liegt daran, dass das gesamte /phpipam/-Verzeichnis des Containers auf den Storage Account zeigt; und der ist leer.
Also phpIPAM herunterladen, entpacken und auf den Storage Account unter File Shares in das File Share „phpipam“ hochladen. Anschließend der Installationsanleitung folgen.

Ist die Installation erfolgreich abgeschlossen und die Anmeldung geglückt, so steht dem dem Autodiscovery von Netzwerk-Komponenten nur noch die Freischaltung von ICMP im Wege.
Dazu wird in den erforderlichen Network Security Groups (kurz NSG) eine Inbound und eine Outbound Regel erstellt, welche ICMP erlaubt. In der Regel sieht das so ähnlich aus.

Also nun viel Spaß dabei die Inhalte zu pflegen, Netzwerk-Komponenten zu entdecken und eine ordentliche Arbeitsgrundlage zu haben.