HÄVG RZ Dev Blog

Hier schreiben die Mitarbeiterinnen und Mitarbeiter aus dem HÄVG Rechenzentrum!

Einsatz von Go im produktiven Umfeld

Dies ist ein sehr technischer Beitrag. Wenn bei dir Fragen aufkommen, schreib uns doch einfach auf unseren Social Media Kanälen an.

Einführung

Seit langem betreiben wir eine Software geschrieben in C# mit der Laufzeitumgebung Mono Runtime, dies in einem diversen Umfeld von Kundensystemen. Wir sprechen hier von Windows, Linux und macOS in der verschiedensten Varianten und Versionen.

Übersicht der eingesetzten Betriebssysteme

Übersicht der eingesetzten Betriebssysteme

Übersicht der verschiedenen Betriebssystemvarianten (z.B. Windows 10 LTSC 2019)

Übersicht der verschiedenen Betriebssystemvarianten (z.B. Windows 10 LTSC 2019)

Die Mono Runtime, wie auch die .NET (Core) Runtime, haben vielzählige Abhängigkeiten zum Betriebssystem. Hier als Beispiel, die Abhängigkeiten zum Host auf einem Linux Alpine System:

  1. icu-libs
  2. krb5-libs
  3. libgcc
  4. libintl
  5. libssl1.1 (Alpine v3.9 or greater)
  6. libssl1.0 (Alpine v3.8 or lower)
  7. libstdc++
  8. zlib

Quelle: https://docs.microsoft.com/en-us/dotnet/core/install/linux-alpine#dependencies

Betroffen sind u.a. kryptografische Implementierungen, diese werden als Primitive nicht von der .NET Runtime implementiert. So wird z.B. bei TLS (SSL) auf das Betriebssystem zurückgegriffen. Dies hat einen sehr guten Grund, so möchte man, dass TLS nach Separation Of Concern von den Experten umgesetzt und gewartet wird, hier OpenSSL mit libssl.

Dazu kann man sich gut die Platform Adaptation (Abstraction) Layer (PAL) im Repro von .NET anschauen. Siehe hier https://github.com/dotnet/coreclr/tree/master/src/pal.

Hintergrund wieso dies kryptografische Implementierungen trifft

Cryptographic operations in .NET Core and .NET 5 are done by operating system (OS) libraries. This dependency has advantages:

  • .NET apps benefit from OS reliability. Keeping cryptography libraries safe from vulnerabilities is a high priority for OS vendors. To do that, they provide updates that system administrators should be applying.
  • .NET apps have access to FIPS-validated algorithms if the OS libraries are FIPS-validated.

The dependency on OS libraries also means that .NET apps can only use cryptographic features that the OS supports. While all platforms support certain core features, some features that .NET supports can’t be used on some platforms. This article identifies the features that are supported on each platform.

Quelle: https://docs.microsoft.com/de-de/dotnet/standard/security/cross-platform-cryptography

Vergleich von Go und .NET 5

Um nur zwei Unterschiede von .NET und Go zu demonstrieren hier ein kleines Experiment. Die Ausgangslage ist nicht ganz fair und wird hier stark verallgemeinert um den Beitrag übersichtlich zu halten.

Wir schreiben einen kleinen TLS-Web-Service in .NET 5 und in Go 1.16.

Dies ist mit der .NET command-line interface (CLI) sehr einfach. Mit dem Befehl dotnet new api haben wir ein funktionierendes Testprojekt, dieses wird dann mit -p:PublishSingleFile=true erstellt.

Und in Go haben wir einen TLS-Web-Service in 19 Zeilen Code geschrieben.

package main

import (
	"log"
	"net/http"
)

func HelloServer(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	w.Write([]byte("This is an example server.\n"))
}

func main() {
	http.HandleFunc("/hello", HelloServer)
	err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

Nun starten wir beide Anwendungen und rufen die https Url auf, während dessen überwachen wir mit Process Monitor die Zugriffe auf Dynamic Link Libraries (DLLs).

Zugriffe auf Dynamic Link Libraries (DLLs)

Zugriffe auf Dynamic Link Libraries (DLLs)

Anhand von diesem Experiment sehen wir, dass die .NET 5 Anwendung doppelt zu viele DLLs benötigt.

Auch der Unterschied der Dateigröße ist erwähnungswert.

.NET: -p:PublishSingleFile=true -p:PublishTrimmed=true
Go:   -ldflags="-s -w"
Dateigröße der ausführbaren Dateien

Dateigröße der ausführbaren Dateien

Die .NET Anwendung ist um den Faktor 7 größer als die Go Anwendung, die Dateigröße kann mit Tooling wie upx weiter reduziert werden, jedoch sollte man sich das gut überlegen.

Unsere Herausforderungen mit Runtime .NET bzw. Mono

Wir kommen bei unseren Kunden auf rund 30 verschiedene Betriebssysteme und Versionen, dies allein wäre kein Problem, wenn nicht manche Systeme durch fehlerhafte Konfiguration die notwendige Implementierung nicht bereitstellen können.

Wir haben viel Zeit in die Analyse der Probleme unserer Software beim Kunden gesteckt und immer mal wieder waren die Abhängigkeiten zum Host die Ursache.

So haben wir z.B. manche Systeme, die nicht in der Lage sind, unser 4096Bit RSA Client Certificates in der .NET Runtime an eine TLS Verbindung anzuhängen. Dies ist ein zwingend notwendiges Feature. Und die Lösung für solche Systeme ist leider - so blöd es auch klingt - eine Neuinstallation oder auch mal ein Upgrade der Betriebssystemversion.

Auch wird auf manchen Systemen weder TLS 1.3 noch TLS 1.2 unterstützt, welche für unsere Infrastruktur zwingend notwendig ist.

Die IT-Sicherheitsrichtlinie der Kassenärztlichen Vereinigung, welche ab dem 1. April 2021 in den ersten Punkten wirksam wird, schränkt leider den Betrieb nicht auf Betriebssysteme ein, welche sich ausdrücklich im Support durch den Hersteller befinden.

Als Lösung eine andere Programmiersprache?

Im Unternehmenskontext ist es kaum denkbar mal eben für ein Produkt (von gut 70 Produkten in der Eigenentwicklung) die etablierte Sprache C# durch Go auszutauschen.

Die Gründe sind vielschichtig, angefangen vom aktuellen Expertenstatus der Softwareentwicklerinnen und Softwareentwickler bis hin zum etablierten Tooling. Bei uns ist C# genau die richtigte Sprache mit der wir unsere Business Logic in ihrer enormen Komplexität abbilden können. D.h. in den meisten Fälle sind wir perfekt aufgestellt.
Das Risiko bei einer solchen Aufstellung ist jedoch, dass wenn man den Hammer meistert nun jedes Problem ein Nagel ist. Aber ein neues Werkzeug zu beherschen kostet auf der anderen Seite viel Zeit und Energie. Gerade wenn mit einer neuen Sprache auch neue Programmierparadigmen kommen.

Die steigende Beliebtheit von Go kam gerade recht, sodass ich über zahlreiche Blog Artikel gestolpert bin. Was mich anfangs besonders interessierte, war die Runtime und das Tooling. Da Go-Kompilate statisch gelinkte Binaries sind, die eine minimale Abhängigkeit zum Betriebssystem haben, habe ich mir Go genauer angeschaut.

Aber viel wichtiger ist natürlich die Sicherheit in den kryptografischen Implementierungen, hier sind im Go Team bei Google natürlich Kryptographieexperten beschäftigt.

Da bei uns hauptsächlich C# eingesetzt wird, musste die Sprache nun schon sehr überzeugen …

Einstieg in Go

Go ist eine kompilierbare Programmiersprache, die Nebenläufigkeit unterstützt und über eine automatische Speicherbereinigung verfügt. Entwickelt wurde Go von Mitarbeitern des Unternehmens Google Inc. Die Entwürfe stammen von Robert Griesemer, Rob Pike und Ken Thompson.

Quelle: https://de.wikipedia.org/wiki/Go_(Programmiersprache)

Go ist schon eine sehr spannende Sprache, als erfahrener Entwickler kann man sich einfach die Spezifikation anschauen, siehe hier: https://golang.org/ref/spec.

Gegenüber Java mit 750 Seiten, ist die Go Spezifikation nur 50 Seiten lang.

Ansonsten kann ich Go by Example empfehlen, hier hat man schnell die grundlegenden Features ausprobiert.

Auch kann man in diese tollen OpenSource-Projekte reinschnuppern:

Umfassender Einstieg in Go: heise.de - Ein Einstieg in die Programmiersprache Go, Teil 1

Erste Erprobung von Go

Zuerst haben wir für unsere Kunden eine Analyse-Anwendung entwickelt, diese sollte im Problemfall gestartet werden und in der Ausgabe werden zahlreiche Funktions- und Konnektivitäts-Tests durchgeführt.

Diese Anwendung hat gezeigt, dass unsere bestehenden Probleme nicht unter Go auftreten!

Selbst wenn der Schalter CGO_ENABLED, der Abhängigkeiten zum Host-System zulässt, gesetzt ist.

Diese Analyse-Anwendung haben wir stetig erweitert, um mehr Erfahrung zu sammeln. Auch habe wir in der Release-Pipeline Erfahrungen mit Kompression gesammelt, um das Kompilat in der Größe zu reduzieren. Die Go-Kompilate sind recht groß, da diese auf eine schnelle Ausführungsgeschwindigkeit optimiert sind. Dies ist meistens kein Problem, aber beim Herunterladen könnte dies manche Kunden stören.

Einsatz von Go im produktiven Umfeld

Nun haben wir 2021 erstmalig Go im produktiven Umfeld eingesetzt, vorerst nur in einem kleinen Funktionsumfang. Aber selbst in diesem kleinen Rahmen konnten wir einige Probleme lösen und unseren Kunden damit Vorteile bringen.

Für Anfang 2022 wird dann ein sehr elementare Teil unsere Anwendung in Go ausgeliefert. Daran sind bereits mehrere Entwickler- und Projektleiter-Teams beteiligt.

Wir versprechen uns nicht nur eine bessere Betriebssicherheit, auch bessere Performance, TLS 1.3 und vielleicht auch bald Post-Quantum-Kryptografie, siehe dazu die Handlungsempfehlungen des BSI zur “Migration zu Post-Quanten-Kryptografie”.

Unseren Softwareentwicklern bieten wir dazu in verschiedenen Formaten Ressourcen und Zeit, um sich in Go voll einbringen zu können. Und vielleicht steigt bei uns die Relevanz in Go noch in weiteren zukünftigen Projekten 😉

Ausblick

Was Anfang 2022 genau kommt, werde ich vielleicht zu einem späteren Zeitpunkt ausführlich vorstellen.

Viele Grüße

Daniel