Hoch verfügbar, strapazierfähig, robust und hoch skalierbar sind großartige Eigenschaften einer modernen Software Architektur. Spring Boot ist das Framework der Wahl für Microservice Architekturen. Angereichert mit dem Spring Cloud Stack (zu großen Teilen Netflix OSS) ergibt sich unter Berücksichtigung einiger Spielregeln eine homogene Microservice Architektur. Das Thema „Sicherheit“ in verteilten Systemen wird erfahrungsgemäß selbst in behördlichen Einrichtungen häufig vernachlässigt.

Seit Spring Boot 2 wird dem Nutzer das Thema jedoch etwas deutlicher präsentiert: Spring Security ist ab sofort per default aktiviert, sodass sich der versierte Spring Entwickler von nun an Gedanken über Sicherheit machen muss.

Weiterhin können URL Redirects, wie sie z.B. bei dem Protokoll OAuth2 vorkommen,  in virtualisierten Umgebungen zu Problemen führen.

Dieser Artikel wirkt einigen Pitfalls entgegen und zeigt die Korrekte Konfiguration für eine auf Spring Cloud basierende Microservices Applikation. Grundkenntnisse in Spring Cloud Applikationen werden vorausgesetzt.

Architektur der Gesamtapplikation

Als Grundlage nehmen wir folgendes simples Architekturschaubild:

microservice_architektur_schaubild

Weiterhin werden folgende Annahmen getroffen:

  • In diesem Fall, laufen alle Services (Client, Zuul, Eureka, OAuth2) in einem eigenen Docker Container.
  • Alle Docker Container befinden sich im gleichen Netzwerk.
  • Die Host IpAdresse lautet: 192.168.99.101
  • Der Zuul Service lauscht auf dem Port 9999.

Damit die Applikationen in virtualisierten Umgebungen laufen und Zuul die korrekten IpAdressen verwendet, sind diverse Konfigurationen an den Services über die Property-Dateien nötig.

Autokonfiguration mittels Eureka Service Discovery

Die gute Nachricht zuerst: Zuul als API Gateway lässt sich mit einer Dependency zu spring-cloud-starter-netflix-eureka-client und einer @EnableDiscoveryClient Annotation automatisch „vor-„konfigurieren. Zuul holt sich die Informationen von dem Eureka Service und legt automatisch die Routen zu dem Namen des Services an. Ein Get-Request auf den routes[1] Endpoint des Zuul Services sollte dann etwa folgendes Ergebnis für den Client Service liefern:

{
„/client/**“:

„id“: „client“,

„fullPath“: „/client/**“,

„location“: „client“,

„path“: „/client/**“,

„retryable“: false,

„customSensitiveHeaders“: false,

„prefixStripped“: true

},“¦

Das Problem bei der automatisch erzeugten Route ist allerdings schnell nach einem Test der Applikation ersichtlich.

strip-prefix-Attribute:

Zuul schneidet den Präfix der angeforderten Resource nach einem Redirect weg. Bspw.: wird aus einer URL http://192.168.99.101:9999/client/login (bzw. der URI /client/login) die URL http://192.168.99.101:9999/login (respektive der URI /login) angefordert. Zuul erkennt ohne den Präfix „/client“ nicht mehr, um welchen Service es sich ursprünglich gehandelt hat. Dazu ist die von Eureka ermittelte Route mit einer Property zu überschreiben. In der application.yml des Zuul Services ist lediglich folgender Eintrag nötig:

zuul:

  routes:

    client:

      service-id: client

      strip-prefix: false

Damit wird die Konfiguration des Service „client“ mit dem Attribut strip-prefix: false (default ist true) überschrieben.

Weiterhin ist es nötig die Property server.servlet.contextPath in der application.yml des Client Service auf den Wert /client zu ändern. Dies zwingt jeden request ein /client voranzustellen. Wir nutzen diese Konvention um die Weiterleitung durch den Zuul Service auf den Client Service zu gewährleisten. Da jeder request ein /client vorangestellt hat, „šmatched“˜ es mit der routing Konfiguration die wir weiter oben in dem Abschnitt

„Autokonfiguration mittels Eureka Service Discovery:“ durch Eureka vorkonfiguriert bekommen haben.

Anmerkung:

Evtl. Ist es in bestehenden Anwendung nicht möglich den contextPath zu ändern oder einfach schlichtweg nicht gewünscht. Man kann die Route auch feingranularer Konfigurieren, jedoch möchte ich mir dies mittels Konvention ersparen. (Stichwort: Convention over Konfiguration)

sensitive-headers Attribute

Der Zuul Service in seiner default Konfiguration gibt empfindliche Informationen wie Cookies und Authorisierungsheader nicht an den angefragten Service weiter, sodass diese bei der Authentifizierung beim angefragten Authorisierungs-Service fehlen. Für unser Szenario erweitern wir die Zuul Konfiguration für alle Routen mit dem Attribut SensitiveHeader mit einem leeren Wert, sodass alle Informationen an den Downstream weitergegeben werden. Unsere Konfiguration sieht in etwa wie folgt aus:

zuul:

  sensitive-headers:

  routes:

    client:

      service-id: client

      strip-prefix: false

Anmerkung:
Auch hier kann kann es sinnvoll sein das Attribute auf dem Level einer bestimmten Route eines Services zu konfigurieren. Statt auf dem Root Level der Zuul Konfiguration würden die Header Informationen nur für einen oder mehrere bestimmte Services weitergereicht werden.

add-host-header Attribute

Ist eine OAuth2 Dependency im Classpath vorhanden und es wird eine Resource requested, die nicht auf einer Whitelist[2] steht, wird automatisch ein redirect auf den Endpoint /login abgesetzt. Zuul würde in den Standardkonfiguration versuchen, den Request auf die Ip-Adresse des Hosts abzusetzen. Im Falle von Container Virtualisierungslösung (Docker) oder Cloud Infrastrukturen (Aws, Azure) würde man eine Timout-Exception erwarten dürfen. Sieht man sich den angefragten Request Header des /login Endpoints einmal an sieht man in dem Attribut Location die IpAdresse (Bspw: 172.0.0.8) der Container Instanz. Diese ist meist nur in dem eigenen virtuellen Netzwerk erreichbar und nicht von außerhalb bzw. dem Browser. Um eine Weiterleitung an den Service gewährleisten zu können, benötigt man die Host Adresse des Zuul Services. Damit ist die Host Adresse (Bspw: 192.168.99.101) der Docker Machine und der Port zu dem Zuul Service, der in der docker-compose.yml mittels Port: „“ 9999:9999 nach Außen bekannt gemacht wurde gemeint. Dazu wird die Zuul Konfiguration in der application.yml wie folgt erweitert:

zuul:

  sensitive-headers:

  add-host-header: true

  routes:

    client:

      service-id: client

      strip-prefix: false

Fazit

Wenn es um Microservice Architekturen geht, ist Spring Boot / Cloud das Framework der Wahl. Mit Spring Cloud erhalten wir die Netflix Oss Abhängigkeiten, jedoch mit der Möglichkeit des Supports von Pivotal.

Entscheidet man sich für das automatisch erzeugte Routing in Zuul mittels Eureka Service Discovery (wie weiter oben beschrieben) sollte man berücksichtigen, dass die erzeugten Routen anzupassen sind, sofern die Applikation nicht nur auf „Localhost“ sondern in virtualisierten Umgebungen wie bspw. Docker laufen.

Die wichtigsten Änderungen in den Zuul Properties sind:

  • add-host-header: true
  • sensitive-header:
  • strip-prefix: false

[1] Neuer Actuator Endpoint in Spring 2:

http://192.168.99.101:9999/zuul/actuator/routes/details

[2] Wird die Klasse WebSecurityAdapter überschrieben und mittels @Configuration zum ApplicationContext hinzugefügt, kann man die Methode

@Override

public void configure(HttpSecurity http) throws Exception {}

überschreiben und Ausnahmen hinzufügen.

Alle Beiträge von pascalstieber

2 Kommentare

Antwort an pascalstieber Antwort verwerfen