DynamicIP Remote Access / Fernzugriff für Softwareentwickler mit maßgeschneiderter Lösung (Python +Terraform +GitLab)
Die Herausforderung der DynamicIP
Wenn die Entwickler von Berg Software von zu Hause aus arbeiten (wie die meisten Menschen heutzutage), verwenden sie ein VPN, um eine sichere Verbindung zu unserer Infrastruktur am Standort herzustellen. Dies ist in einigen Fällen vollkommen ausreichend. Beim Zugriff auf die Nicht-Produktions-Infrastruktur gestaltet sich die Situation jedoch ein wenig komplexer:
- Erstens ist es aufgrund des aktuellen Stands der Technik eine Herausforderung: Das Hosting der Nicht-Produktions-Infrastruktur in der Cloud (d. h. „IaC/Infrastructure as Code“) ist derzeit nicht bloß eine von vielen Optionen, sondern die Norm – es geht also kein Weg daran vorbei.
- Zweitens geht es um die Risiken: Die Nicht-Produktionsrechner (auf denen die Entwicklung, das Testen und die Qualitätssicherung erfolgen) sind etwas, auf das niemand außerhalb des Entwicklerteams Zugriff haben sollte (und das schließt den Client, Indizierungs-Bots usw. ein). Die Nicht-Produktionsrechner sind daher vom Internet abgetrennt, was sie generell unzugänglich macht.
- Drittens, aber nicht zuletzt, geht es um den VPN-Zugang selbst: Um den Zugriff auf einen fremden Rechner zu ermöglichen, erlauben alle Firewalls nur den Zugriff auf bestimmte IP-Adressen. Die IP-Adresse einer Person bleibt jedoch nicht immer zu 100 % unverändert: Es kann sein, dass jemand seinen Router zurücksetzt oder seinen Dienstanbieter dazu veranlasst, diesen „einfach so“ zurückzusetzen. In diesen Fällen muss unser Operations-Team die IP-Liste aktualisieren – was mühsam, langsam und fehleranfällig sein kann.
Wir haben uns daher eine Dynamic IP-Lösung ausgedacht, die zwei Dinge erfüllen soll:
- etwas Bekanntes (d. h. einen FQDN/Fully Qualified Domain Name) in eine IP-Adresse umzuwandeln (wird von den Firewalls zum Erstellen der Zugriffsregeln benötigt);
- einen dynamischen DNS-Dienst zu verwenden, um den FQDN mit der neuen IP-Adresse des Entwicklerrechners zu aktualisieren.
Unsere Lösung war ein automatisiertes, zeitgesteuertes Tool, das die Aktualisierung der Firewall/IP-Liste automatisiert und damit Eingriffe der Operationsabteilung überflüssig macht sowie Wartezeiten verkürzt.
Das dynamische DNS
Um einen DNS-Dienst auszuwählen, haben wir den damaligen Status-quo genutzt, um die Anforderungen zu entwickeln:
- Er sollte uns erlauben, Subdomains unter unserem Hauptdomainnamen zu verwenden.
- Er sollte dem Operations-Team erlauben, Subdomains nach Bedarf zu erstellen/zu entfernen.
- Es sollte nicht erforderlich sein, dass Entwickler eigene Konten erstellten, und er sollte dem Operations-Team erlauben, den aktualisierten, personalisierten Link für jeden Entwickler in einer zentralisierten Konsole zu generieren.
- Es sollte keine Anwendung (/kein komplizierter Prozess) erforderlich sein, um den FQDN zu aktualisieren.
- Die generierten Links sollten kein automatisches Ablaufdatum haben. Wenn einer unserer Entwickler eine statische IP-Adresse hatte, sollte er seinen FQDN nicht aktualisieren müssen.
Darauf aufbauend haben wir uns für das Premium-DNS-Paket von ClouDNS entschieden: Es verlangt von unseren Entwicklern, einen Link in ihrem regulären Browser als Lesezeichen zu setzen und ihn dann anzuklicken/zu öffnen, um ihren jeweiligen FQDN-Eintrag zu aktualisieren.
Nachdem der FQDN-Eintrag mit der Information über die aktualisierte IP des Entwicklers verfügbar war, mussten wir die Firewall-Informationen aktualisieren, sobald sich die IP-Adresse änderte. Durch die Analyse unserer regulären Tools und Technologien konnten wir drei davon wie folgt kombinieren:
- Python: übersetzt den FQDN in eine IP-Adresse; und führt eine Liste der letzten bekannten IP-Adressen, damit wir überprüfen können, ob sich etwas geändert hat. (Dann – nur wenn sich etwas geändert hat – führt man die Skripte aus, um die Firewall zu aktualisieren).
- Terraform: aktualisiert die Firewall auf unseren Rechnern.
- GitLab CI/CD Scheduler: führt den Workflow alle paar Minuten aus.
Schauen wir uns also jedes einzelne davon an:
Python
Wir entschieden uns, Python zu verwenden, um den DNS-Teil der Herausforderung zu lösen, weil:
- wir die Socket-Bibliothek zur Verfügung haben
- und socket.gethostbyname(hostname) verwenden können, um basierend auf dem FQDN die entsprechende IP-Adresse abzurufen.
Unser Skript hat drei Hauptziele:
- alle FQDNs in IP-Adressen umzuwandeln,
- zu überprüfen, ob eine der IP-Adressen nicht mit dem vorherigen Run-Status übereinstimmt, und bei Änderungen zu aktualisieren,
- basierend auf einer Vorlage, die die OpenTelekomCloud-Sicherheitsgruppen-Informationen enthält, eine neue Terraform-Datei zu erstellen, die die aktualisierte IP-Adresse beinhaltet.
Hier ist der Auszug aus dem Skript:
#!/usr/bin/env python3 import socket import sys import os ipfile = 'ci/dynip/oldips' hostnames = ["DeveloperFirstName.DeveloperLastName.dyn.myDomain.com"] ips="" old_ips="" i=1 for hostname in hostnames: print(hostname) ip = socket.gethostbyname(hostname) ips=ips+'{ip = "'+ip+'/32",name = "'+hostname+'"}' if i != len(hostnames): ips=ips+",\n" i=i+1 print('Discovered ips:') print(ips) ### Here tyo add check if the string ips is different to the one we have in the status file. ### If different then continue - else exit def inplace_change(filename_tmpl, filename_dest, old_string, new_string): # Safely read the input filename using 'with' with open(filename_tmpl) as fr: s = fr.read() if old_string not in s: print('"{old_string}" not found in {filename}.'.format(**locals())) return # Safely write the changed content, if found in the file with open(filename_dest, 'w') as fw: print('Changing "{old_string}" to "{new_string}" in {filename_tmpl}'.format(**locals())) s = s.replace(old_string, new_string) fw.write(s) ## inplace_change('ci/dynip/secgroups-dynip-tmpl','secgroups-dynip.tf','!!!ADDRESSES!!!',ips) def read_file(filePathOldIps): with open(filePathOldIps) as fr: s = fr.read() print('Old ips:') print('"{s}" found in "{filePathOldIps}".'.format(**locals())) return s def write_file(filePathOldIps, newIps): with open(filePathOldIps, 'w+') as fw: fw.write(newIps) print('ips written in {filename}.') if os.path.isfile(ipfile): old_ips = read_file(ipfile) if old_ips == ips: print('no ip was changed') sys.exit(666) else: print('ips were changed') write_file(ipfile,ips) inplace_change('ci/dynip/secgroups-dynip-tmpl','secgroups-dynip.tf','!!!ADDRESSES!!!',ips)
Terraform
Um die Wartung und das Änderungsmanagement unserer Cloud-Infrastruktur zu unterstützen, haben wir die Infrastructure-as-a-Code-Lösung von Terraform mit den OpenTelekomCloud-Modulen verwendet. (Dies liegt daran, dass wir die OTC-Cloud nutzen. Das gleiche Ergebnis kann durch AWS/Azure mit ihren jeweiligen Modulen erreicht werden).
Hierbei stießen wir auf die nächsten Herausforderungen: Da das Skript automatisch ausgeführt wurde, brauchten wir die Möglichkeit, die Änderungen nur für die spezifische Sicherheitsgruppe automatisch zu planen, zu genehmigen und anzuwenden. Alle anderen Änderungen würden ignoriert werden (z. B. alle Änderungen, die durch ein neues Basis-Image verursacht werden könnten, oder jede unvollständig geprüfte Änderung, die im Repo vorgenommen werden würde).
Terraform bietet eine elegante Lösung für diese Herausforderung, da es uns erlaubt, nur eine bestimmte Terraform-Datei auszuführen und die Übernahme von Änderungen automatisch zu genehmigen.
Dies kann folgendermaßen umgesetzt werden:
./ci/app/terraform plan -target=opentelekomcloud_compute_secgroup_v2.secgrp_everithyng_dynip -out=project-plan02.dat |& tee -a ./logs/plan-dyn-$today.log ./ci/app/terraform apply -var-file="./ci/secrets/secret.tfvars" -auto-approve -target=opentelekomcloud_compute_secgroup_v2.secgrp_everithyng_dynip |& tee -a ./logs/planautopply-dyn-$today.log
GitLab CI/CD
Wir verwenden GitLab.com zur Versionskontrolle und zum Ausführen der CI/CD-Pipelines unseres Projekts. Daher mussten wir das gitlabci-Skript erweitern, um einen neuen Schritt hinzuzufügen, der basierend auf einer Bedingung ausgeführt werden kann. Dieser Schritt sollte der Einzige sein, der ausgeführt wird, wenn wir die Pipeline vom Scheduler aus starten.
Details zu diesem Schritt:
dynip-otc: stage: dynip before_script: - apk --no-cache add bash git openssl py-pip script: - ./ci/bin/x_dynsecurity.sh tags: - project artifacts: paths: - .terraform/ - project-plan02.dat - logs/ expire_in: 1 week only: refs: - master variables: - $SCHEDULED_DYNIP =~ /^true*/
Fazit
Durch die Verwendung von lediglich drei einfachen und leicht zugänglichen Tools (Python, Terraform, GitLab) waren wir in der Lage
- unseren Softwareentwicklern ungestörtes Arbeiten von zu Hause aus zu ermöglichen,
- mit nahezu Echtzeit-IP-Updates,
- ohne umfangreiche/komplizierte Systeme
- und ohne eigenes Personal für manuelle Handlungen/Aktualisierungen.
Und wie machen *Sie* das? Möchten Sie Ihre Lösung mit uns teilen?