Security in microservices
BLOG | LEESTIJD: 4 MINUTEN
In de huidige markt waar bedrijven gebruikmaken van uiteenlopende softwarearchitecturen en applicaties, krijg je amper het gevoel dat je data helemaal veilig is. Wanneer je applicaties bouwt op basis van een microservices-architectuur zijn deze security-issues nóg belangrijker. Binnen deze architectuur communiceren individuele diensten immers met elkaar én met de client.
Wat zijn microservices?
Er bestaan verschillende architectuurstijlen. Microservices is er daar een van. Deze architectuur krijgt veel aandacht in de techwereld. Grote publieke organisaties zijn druk bezig met de ontwikkeling van microservices. Bedrijven zoals Amazon en Netflix zweren er zelfs bij.
Er bestaat geen eenduidige definitie hoe deze architectuurstijl precies in elkaar steekt. Een microservice is een monofunctionele component. Een microservice voert niet meer dan één logische functie uit. De essentie is vaak dat één grote applicatie, een zogenaamde monoliet, wordt opgeknipt in meerdere, kleine services: één per operatie (de microservice). Al deze losse services communiceren met elkaar om uiteindelijk functioneel hetzelfde te doen als één monolithische applicatie.
De overstap naar microservices
Microservices, ook bekend onder de naam microservice-architectuur, bouwt een applicatie op als een verzameling kleine, autonome services die gemodelleerd zijn rond één bedrijfsdomein. Het zijn, als het ware, kleine individuele diensten die met elkaar communiceren over één bedrijfslogica. Wanneer bedrijven overstappen van een monolithische architectuur naar microservices zien ze veel voordelen: schaalbaarheid, flexibiliteit en korte ontwikkelcycli. Tegelijkertijd brengt deze architectuur een aantal complexe uitdagingen met zich mee.
Designkeuzes en subtypes
Zodra je inzicht hebt in alle services die een monolithische applicatie uitvoert en de transacties die daarbij horen, heb je per microservice grofweg de keuze uit twee verschillende servicetypes. Je hebt de zogenaamde ‘taakservices’: services die een specifieke taak uitvoeren (bijvoorbeeld het ophalen van een bestand). Taakservices kunnen meestal prima asynchroon opereren. ‘Transactieservices’ is een service die de uitvoering van taakservices binnen de context van de logische transactie coördineert, die door de microservice wordt uitgevoerd. Over het algemeen dienen transactieservices vaak synchroon te lopen. Asynchrone berichten brengen enkele voordelen in de architectuur met zich mee maar ook uitdagingen. Met een servicenet zoals Linkerd of Istio die als proxyservice fungeert voor het netwerkverkeer tussen services, kun je de uitdagingen die met asynchrone berichten worden geassocieerd het hoofd bieden. Met het oog op flexibiliteit, schaalbaarheid en beveiliging zien we dat microservices vooral met container orkestratie tools draaien zoals kubernetes. Met een goed doordacht ontwerp van microservices met servicenet, authenticatie, de juiste hardening en het gebruik van moderne tools, waaronder containerorkestratie- en API-management, krijg je vandaag de dag de modernste microservices.
Uitdagingen bij de beveiliging van microservices
- Niet elke keer verifiëren
Stel je het scenario voor waarbij een gebruiker telkens moet inloggen om toegang te krijgen tot een bron. In een microservices-architectuur dienen logingegevens op zo’n manier opgeslagen te worden, dat de gebruiker zich niet elke keer hoeft te authenticeren wanneer hij toegang tot een bron probeert te krijgen. Dit vormt echter een uitdaging. Hoe beveilig je deze opgeslagen logingegevens? Hoe voorkom je dat ze door een derde partij benaderd kunnen worden?
- De beveiliging van individuele microservices
De beveiliging van elke individuele microservice vormt eveneens een uitdaging. In deze architectuurstijl communiceren niet alleen microservices tegelijkertijd met elkaar, ze communiceren ook met applicaties van derde partijen. Stel: een klant logt in vanuit een applicatie van een derde partij. Je dient er dan zeker van te zijn dat deze client geen toegang krijgt tot de data van microservices waarbij hij er oneigenlijk gebruik van kan maken.
Best practices voor de beveiliging van microservices
De basis is meestal OWASP; deze blijft heel belangrijk.
Tokens en het Defense In Depth-mechanisme
Het Defense in Depth-mechanisme is, eenvoudig gezegd, een mechanisme waarmee je beveiligingslagen kunt aanbrengen om services te beschermen. Als ontwikkelaar hoef je slechts de services te identificeren met de meest gevoelige informatie. Vervolgens breng je een aantal beveiligingslagen aan ter bescherming van deze informatie. Wil je de security van gevoelige services op voldoende niveau krijgen met het Defense In Depth-mechanisme dan sta je al snel voor de uitdaging om het aantal secrets binnen het systeem beheersbaar te houden.
De meest cruciale uitdaging vormen de tokens die gebruikersinformatie opslaan. De gegevens van tokens zal versleuteld moeten worden om te voorkomen dat bronnen van derde partijen er gebruik van kunnen maken. JSON Web Format, beter bekend onder de afkorting JWT, is een open standaard die het formaat van het token definieert, bibliotheken voor verschillende talen biedt en deze tokens versleutelt.
Met een BDI-framework zoals Jadex kun je encryptie en authenticatie op berichtenniveau regelen, waardoor dit automatisch plaatsvindt op de servicelaag. Dat draagt bij aan de beheersbaarheid en ondersteunt ook de DevOps-werkwijze. Om dit goed te kunnen doen, is een diepgaand begrip van cryptografie en handshake protocollen onmisbaar, bijvoorbeeld J-PAKE.
Het volgende punt is misschien een ‘no-brainer’: codeer geen wachtwoorden in je code of properties maar maak gebruik van secrets, bijvoorbeeld Terraform Vault. Zorg er ook voor dat beheerprocedures goed zijn geregeld, zodat je ook in een continu spectrum het beheer en roteren van keys, API tokens, wachtwoorden, et cetera goed hebt geborgd.
Beveiliging in het post-kwantumtijdperk
Hiernaast ziet u de zogenaamde “Bombe”. Aan het eind van de tweede wereldoorlog had de Amerikaanse Marine circa 120 elektromechanische machines in gebruik om onderschepte boodschappen van Duitse Enigma machines te ontcijferen. Hij werd door Alan Turing ontwikkeld samen met een team van engineers en wiskundigen en werd daarom ook wel de “Turing Bombe” genoemd. Zij speelden een sleutelrol in de elektronische oorlogvoering in de tweede wereldoorlog en waren onmisbaar in de strijd tegen onder meer de Duitse U-boten. Bron: De NSA, National Security Agency.
Inmiddels zijn er kwantumcomputers. Bij kwantumcomputing voldoet de versleuteling die voorheen afdoende was, niet meer. De community zit in deze post-kwantum tijd gelukkig niet stil. De knapste cryptominds hebben zich verenigd in het Open Quantum Safe (OQS)-project en komen met praktisch uitvoerbare oplossingen. Binnen de veiligheidsketen heb je veelvuldig te maken met hoog gerubriceerde informatie. Afhankelijk van je werkzaamheden kan dit oplopen tot rubriceringen zoals (ZEER) Streng Geheim tot Cosmic Secret. Ook in het bedrijfsleven is het belangrijk geheimen te bewaren, en reputaties en identiteiten van gebruikers en klanten goed te beschermen. Voor het gros van de services zal een moderne cipher met PKI volstaan. Afhankelijk van de rubricering en gevoeligheid van een individuele microservice kun je met bijvoorbeeld NewHope of CECPQ1 (die inmiddels zijn ontsproten uit het OQS-project) het berichtenverkeer versleutelen. Op deze manier kan zelfs een kwantumcomputer een bericht niet direct ontsleutelen.
Microservices in containers
Met moderne containertechnologie kun je microservices van elkaar isoleren, zodat er zonder gedefinieerde route geen verkeer tussen services onderling mogelijk is. Je kunt het enigszins vergelijken met virtualisatie zoals een Virtuele machine (VM), waarbij een VM compleet gescheiden kan draaien, en zich er niet ‘van bewust is’ of er mogelijk nog meerdere VM’s op een en dezelfde host draaien. Deze vorm van abstractie bouwt in elk geval foutisolatie in en brengt ook andere noemenswaardige voordelen met zich mee die je ook kent van virtualisatie. Denk daarbij aan flexibiliteit, beschikbaarheid en schaalbaarheid. Beveiliging is dus een belangrijke reden waarom microservices tegenwoordig vaak binnen containers draaien met behulp van tools zoals Kubernetes (K8S) of Docker Swarm. Voor verkeer naar buiten zul je wel expliciete routes dienen te configureren, al dan niet via een API-gateway.
API Gateways
Om services te beveiligen met tokenverificatie kun je een API-Gateway als extra element toevoegen. De API Gateway fungeert daarbij als een toegangspoort voor alle verzoeken van de client, en verbergt de microservices van de client op een efficiënte manier. De client heeft dus geen directe toegang tot microservices. Het idee is dat op deze manier het voor iedere client onmogelijk is om ook maar één van deze services te gebruiken.
Alhoewel het eenvoudig is om de verantwoordelijkheid voor authenticatie bij de API-gateway te leggen, is dit vaak onvoldoende. Zonder voor de gevoelige microservices meerdere securitylagen in te bouwen met het Defense Indepth Mechanisme wordt de API Gateway zelf de zwakste schakel. Zodra iemand via een kwetsbaarheid in de gateway binnen heeft weten te komen, kan iemand zichzelf toegang verschaffen tot alle verborgen backend-services.
Dependency checks
Veel microservices gebruiken allerlei opensource frameworks die in snel tempo veranderen. Deze hebben veel dependencies met mogelijke kwetsbaarheden. Het wordt steeds belangrijker om geautomatiseerd dependency-checks in je build-proces in te bouwen. Voor de OWASP Dependency Check-tool zijn er plug-ins beschikbaar voor de CI-tools: Maven, Gradle, Jenkins en Ant.
Gedistribueerde tracering
Als je microservices gebruikt, dien je al deze services voortdurend te bewaken. Wanneer je echter een gigantische hoeveelheid services tegelijkertijd moet controleren, wordt dat een haast onmogelijke taak. Je kunt dit voorkomen door gebruik te maken van gedistribueerde tracering (Distributed Tracing). Deze methode stelt je in staat om fouten op te sporen en de achterliggende oorzaak ervan te achterhalen. Zo kun je dus snel en eenvoudig ontdekken welke microservices een beveiligingsprobleem hebben.
Sessiemanagement
Een belangrijke parameter om rekening mee te houden bij de beveiliging van microservices is sessiebeheer. Elke keer wanneer een gebruiker een toepassing start, wordt een sessie op gang gebracht. De data tijdens deze sessie kun je op de volgende manieren laten verwerken:
- Je kunt de data van een sessie van een enkele gebruiker opslaan op een specifieke server. Dit soort systeem is volledig afhankelijk van de taakverdeling tussen de services en is alleen horizontaal schaalbaar.
- De volledige sessiegegevens kunnen in een single-instance opgeslagen worden. Vervolgens kan de data door het hele netwerk gesynchroniseerd worden. De enige uitdaging bij deze methode is dat je netwerkbronnen uitgeput raken.
- Je kunt ervoor zorgen dat de gebruikersdata gehaald wordt uit de opslag van een gedeelde sessie. Zo weet je zeker dat alle services dezelfde sessiegegevens lezen. Omdat de data echter wordt verkregen uit gedeelde opslag, moet je er zeker van zijn dat je een beveiligingsmechanisme hebt. Alleen dan kun je de data immers op een veilige manier benaderen.
Eerste SSL-sessie
De eerste sessie is eigenlijk heel eenvoudig: gebruikers loggen éénmaal in op een applicatie, waarna ze toegang hebben tot alle services van deze applicatie. Voorwaarde is natuurlijk dat elke gebruiker eerst moet communiceren met een authenticatieservice. Dit zal voor veel verkeer zorgen tussen alle services onderling. Mocht ergens iets fout gaan, dan kan het in zo’n geval erg omslachtig zijn om te achterhalen waar het misgaat.
Wederzijdse SSL
Bij wederzijdse SSL hebben applicaties vaak te maken met verkeer van gebruikers, van derde partijen en van microservices die met elkaar communiceren. Aangezien derde partijen deze services benaderen, bestaat er altijd het risico op aanvallen. Een oplossing hiervoor is wederzijdse SSL of wederzijdse authenticatie tussen microservices waardoor de getransporteerde gegevens zijn geëncrypt. Er is echter een complicerende factor: omdat elke service zijn eigen TLS-certificaat heeft, zullen de ontwikkelaars de certificaten moeten updaten.
Gebruik van OAuth
Als je gebruikmaakt van OAuth, dan vraagt de applicatie de gebruiker om de applicaties van derde partijen te autoriseren. Om zodoende de gevraagde informatie te gebruiken en er een token voor te genereren. In het algemeen wordt er een autorisatiecode gebruikt voor het verzoek om een token. Dit om er zeker van te zijn dat de callback-URL van de gebruiker niet gestolen is. Terwijl het toegangstoken wordt verstrekt, communiceert de client met de autorisatieserver. Deze server autoriseert de client om te voorkomen dat anderen de identiteit van de client vervalsen. Wanneer je gebruikmaakt van microservices met OAuth fungeren de services als een client in de OAuth-architecture om de security-issues te vereenvoudigen.
Gebruik van 0Auth 2.0
OAuth 2.0 is inmiddels de norm geworden voor API-bescherming en, als basis voor OpenID Connect, voor identiteitsverificatie. OAuth ging in eerste instantie uit van een statische relatie tussen cliënt, autorisatieserver en resourceservers. De URL’s van autorisatieserver en resource-servers waren bekend bij de client tijdens de implementatie. De validatie van de communicatie (communiceert de client met een legitieme server?), was gebaseerd op TLS-serververificatie. Met de toenemende adoptie van OAuth verdween dit model echter. In verschillende scenario’s werd het vervangen door een dynamische instelling van de relaties tussen clients, en de autorisatie- en resourceservers voor een specifieke implementatie.
Op deze manier kan dezelfde client ingezet worden om toegang te krijgen tot diensten van verschillende aanbieders (in het geval van standaard-API’s, zoals e-mail of OpenID Connect). Dezelfde client kan ook als frontend dienen voor een specifieke tenant in een multi-tenant omgeving. Extensies van OAuth, zoals [RFC7591] en [RFC8414], zijn ontwikkeld om het gebruik van OAuth in dynamische scenario’s te ondersteunen. Dergelijke gebruiksscenario’s bieden echter ook weer openingen voor nieuwe aanvalsmethoden.
Veel services die gebruikmaken van OAuth gebruiken de implicit flow. Het is belangrijk om dit om te zetten naar de authorization code flow. Sta je op het punt te kiezen voor welke authorization flow je gebruikt, volg dan deze best practices. Maak je gebruik van de implicit flow, dan is het raadzaam dit snel aan te passen. In dit filmpje kun je zien waar je dat kunt doen.
Uitdagingen bij het werken met OAuth
OAuth is op verschillende manieren voor uiteenlopende implementaties gebruikt. Daarbij zijn de volgende uitdagingen geconstateerd:
- OAuth-implementaties worden aangevallen via bekende zwakke plekken in de implementatie en antipatronen (CSRF, Referer header). Hoewel de meeste van deze bedreigingen zijn besproken in het OAuth 2.0 Threat Model and Security Considerations [RFC6819], blijkt uit voortdurende exploitatie dat specifiekere aanbevelingen nodig zijn. De bestaande mitigaties kunnen te moeilijk zijn om te implementeren, waardoor een diepgaandere beveiliging is vereist.
- Technologie is veranderd, bijvoorbeeld de manier waarop browsers in sommige situaties met fragmenten omgaan. Hierdoor verandert het vanzelfsprekende onderliggende beveiligingsmodel.
- OAuth wordt toegepast in omgevingen met hogere beveiligingsvereisten dan waar OAuth aanvankelijk voor was bedoeld. Denk daarbij aan Open Banking, eHealth, eGovernment en elektronische handtekeningen. Deze toepassingen vragen om strengere richtlijnen en additionele beveiliging.
- OAuth wordt gebruikt in veel dynamischere setups dan oorspronkelijk was voorzien. Dit brengt vanzelfsprekend ook nieuwe uitdagingen voor security met zich mee. Deze uitdagingen gaan verder dan de oorspronkelijke scope.
Praat ook mee – Wat zijn jouw ervaringen en van welke technieken verwacht jij het meest in 2020 om microservices veilig te houden?