Inhoudsopgave 1 Inleiding 1.1 Gedistribueerde applicaties 1.2 Probleemstelling . . . . . . 1.3 Oplossing . . . . . . . . . . 1.4 Overzicht van de tekst . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
5 5 6 7 9
2 Spec Explorer 2.1 Reactieve systemen . . . . . . 2.2 Modelgebaseerd testen . . . . 2.2.1 Modellering . . . . . . 2.2.2 Exploratie & Scenario 2.2.3 Test generatie . . . . . 2.2.4 Test executie . . . . . 2.2.5 Uitgewerkt voorbeeld 2.2.6 Toestandsinvarianten . 2.3 Besluit . . . . . . . . . . . . .
. . . . . . . . . . . . . . . controle . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
11 12 12 14 16 17 19 19 22 23
3 Filteren met Spec Explorer 3.1 Relevantie van Spec Explorer 3.2 Filters . . . . . . . . . . . . . 3.2.1 Modelprogramma’s . . 3.2.2 XMLFilter . . . . . . 3.2.3 DllFilter . . . . . . . . 3.3 Besluit . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
25 25 25 27 30 34 34
. . . . . . . . . .
36 36 37 37 38 39 39 42 44 46 47
. . . . . .
. . . . . .
. . . . . .
. . . . . .
4 Case study 1: .NET Remoting 4.1 .NET Remoting . . . . . . . . . . . . . . . . . . . . 4.2 Remotable types . . . . . . . . . . . . . . . . . . . 4.3 Object activatie . . . . . . . . . . . . . . . . . . . . 4.4 Kanalen . . . . . . . . . . . . . . . . . . . . . . . . 4.5 Message sinks en channel sinks . . . . . . . . . . . 4.5.1 Channel sinks . . . . . . . . . . . . . . . . . 4.5.2 Message sinks . . . . . . . . . . . . . . . . . 4.6 Custom sink voor application-level stateful firewall 4.7 Moeilijkheden . . . . . . . . . . . . . . . . . . . . . 4.8 Besluit . . . . . . . . . . . . . . . . . . . . . . . . .
1
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
5 Case study 2: ASP.NET 5.1 ASP.NET . . . . . . . 5.2 HTTP modules . . . . 5.2.1 Events . . . . . 5.3 Custom HTTP module 5.4 Moeilijkheden . . . . . 5.5 Besluit . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
48 48 48 49 50 51 51
6 Discussie 6.1 Performantie . . . . . . . . . . . . . . . . . 6.2 XmlFilter: sneller dus beter? . . . . . . . . 6.3 Assembly meerdere keren laden . . . . . . . 6.4 Alternatief voor specificatie: Spec# . . . . 6.5 Alternatieven voor interceptie . . . . . . . . 6.5.1 custom proxies . . . . . . . . . . . . 6.5.2 SOAP filters . . . . . . . . . . . . . 6.5.3 Aspecten . . . . . . . . . . . . . . . 6.5.4 In servercode . . . . . . . . . . . . . 6.6 Alternatief technologie: J2EE . . . . . . . . 6.7 Alternatief firewall: ASP.NET validation . . 6.8 Uitbreidingen . . . . . . . . . . . . . . . . . 6.8.1 XmlFilter performanter . . . . . . . 6.8.2 Niet-primitieve types als parameter .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
53 53 53 54 55 56 56 56 56 56 56 57 57 57 57
. . . . . . . . . . . . . . . . . .
58 58 58 58 59 59 60 60 60 60 60 61 61 61 61 61 62 62 62
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
7 Besluit 7.1 Resultaten . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.1 Beschrijven van verwachte gedrag van clients . 7.1.2 Application-level stateful firewall . . . . . . . . 7.2 Moeilijkheden . . . . . . . . . . . . . . . . . . . . . . . 7.2.1 Gebreken bij xmlFilter . . . . . . . . . . . . . . 7.2.2 Parameters in ASP.NET . . . . . . . . . . . . . 7.2.3 Onderscheppen van output bij ASP.NET . . . 7.3 Kritische bedenkingen . . . . . . . . . . . . . . . . . . 7.3.1 Modelprogramma’s voor 1 client . . . . . . . . 7.3.2 Firewall voor ASP.NET . . . . . . . . . . . . . 7.3.3 xmlFilter vs. dllFilter . . . . . . . . . . . . . . 7.4 Verder onderzoek . . . . . . . . . . . . . . . . . . . . . 7.4.1 Outputstream in ASP.NET . . . . . . . . . . . 7.4.2 Parameters in ASP.NET . . . . . . . . . . . . . 7.4.3 xmlFilter performanter . . . . . . . . . . . . . . 7.4.4 xmlFilter met referenties . . . . . . . . . . . . . 7.4.5 xmlFilter met oneindig veel parameterwaardes 7.5 Conclusie . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
A XmlFilter
63
B DllFilter
67
2
C MethodToFilterSink
69
D InterceptingModule
71
3
Hoofdstuk 1
Inleiding In gedistribueerde applicaties kan men er vaak niet op steunen dat een client zich gedraagt zoals verwacht. Het zou bijvoorbeeld kunnen dat de clientsoftware gehackt wordt om bepaalde controles uit die software te verwijderen. Bij een webapplicatie is het mogelijk dat een aanvaller de applicatie rechtstreeks aanvalt door forceful browsing. Deze aanval bestaat uit het opvragen van pagina’s of bronnen aan de webserver waar je geen toegang zou tot mogen hebben. In sommige gevallen gebeurt dit door een referentie toe te kennen aan een interne pagina van een applicatie. Later kan er dan rechtstreeks naar deze pagina gegaan worden en wordt de authenticatiepagina omzeild. In deze inleiding wordt besproken wat gedistribueerde applicaties en webapplicaties zijn. Vervolgens wordt uitgelegd wat mogelijke beveiligingsproblemen hierbij kunnen zijn. In de laatste sectie schetsen we kort de oplossing voorgesteld in deze thesis.
1.1
Gedistribueerde applicaties
Een gedistribueerd systeem is een systeem waarin componenten zich bevinden op computers, onderling verbonden door een netwerk. Ze communiceren en co¨ordineren hun acties enkel door het doorgeven van berichten.[1] Het bouwen van deze systemen brengt een aantal uitdagingen, zoals heterogeniteit van de componenten, overeenstemming (concurrency) van de componenten, transparantie en uiteraard ook beveiliging, met zich mee. In deze thesis zullen we ons focussen op dit laatste aspect. De meest gebruikte architectuur voor gedistribueerde systemen is het client-servermodel. Figuur 1.1 illustreert de eenvoudige structuur waarin clientprocessen interageren met individuele serverprocessen op verschillende computers. De trust boundary is de scheiding tussen verschillende niveaus van vertrouwen. De server zal behoedzamer moeten omgaan met entiteiten die komen van de client-zijde van de trust boundary. Elke host, computer of programma neemt ofwel de rol aan van server, ofwel van client. Ook de combinatie van beide is mogelijk, zoals bijvoorbeeld een webserver. Een webserver fungeert als server voor de client, maar is zelf een client ten opzichte van de databank die hij aanspreekt. Servers bieden diensten aan, clients maken gebruik van deze diensten. Voorbeelden van clientserver-systemen zijn mail-clients (bijvoorbeeld Outlook (Express)) en mail-servers (telenet, hotmail,...), een online-bankingprogramma,. . . We gaan even dieper in op het online-bankingsysteem. Je installeert thuis een programma waarmee je overschrijvingen kan ingeven, rekeningen kan beheren,. . . Uiteraard heeft het
4
Figuur 1.1: Client-servermodel ge¨ınstalleerde programma zelf geen informatie over je rekeningen. Wanneer je je rekeninginformatie opvraagt in het programma, zal dit de server van de bank contacteren die deze informatie zal teruggeven aan jouw programma. Bij fat clients zal je voor elke applicatie die je wilt gebruiken de clientsoftware moeten installeren op je computer. Dit brengt een aantal problemen met zich mee. Niet alleen zal je computer al snel vol staan met clientsoftware, telkens er een applicatie ge¨ upgraded wordt, zal je een nieuwe versie van de clientsoftware moeten installeren, wens je up-to-date te blijven. Met het enorme succes van het World Wide Web kwam hier een oplossing voor: webapplicaties. Je dient geen clientsoftware meer te installeren, alles kan worden uitgevoerd in een standaard webbrowser. Webapplicaties werden al snel erg populair omdat ze kunnen ge¨ updatet en onderhouden worden zonder dat er nieuwe software moet verspreid en ge¨ınstalleerd worden. Dit is niet alleen een voordeel voor de gebruikers ook voor de softwareproducenten is dit een grote vooruitgang. De distributie van hun producten is sterk vereenvoudigd door gebruik van het Web. Ondertussen kan je webapplicaties niet meer wegdenken uit het weblandschap: webmail, online shoppen, online kalenders, takenlijsten, helpdesk applicaties,. . .
1.2
Probleemstelling
In gedistribueerde applicaties kan men er niet vanuit gaan dat een client zich gedraagt zoals verwacht. Desalniettemin zou men er graag vanuit gaan dat gebruikers handelen volgens een vooropgesteld protocol. Zo kan men aannemen dat een gebruiker enkel die pagina’s bezoekt die toegankelijk zijn via de links die op de hoofdpagina staan. Als men de gebruikers vertrouwt, zijn client-side checks ruim voldoende. Jammer genoeg moet men er in de meeste gevallen vanuit gaan dat een client niet te vertrouwen is. Er zal immers altijd wel iemand
5
zijn die probeert een aantal beveiligingsmaatregelen te omzeilen, dit door bijvoorbeeld de clientsoftware te hacken of te reverse-engineeren om de controles te verwijderen of aan te passen. Zo zijn er veel gedistribueerde applicaties waarbij je bij installatie of registratie een aantal stappen moet doorlopen, zoals je naam invullen, het accepteren van de licentieovereenkomst,. . . Als het mogelijk is om een link bij te houden naar een pagina die volgt op de licentieovereenkomst, kan men de licentieovereenkomst omzeilen. Dit moet uiteraard vermeden worden. Ook bij een webapplicatie is het mogelijk dat een aanvaller de applicatie rechtstreeks aanvalt door het fabriceren van HTTP requests 1 . Als voorbeeld nemen we een webapplicatie die producten verkoopt (zoals bijvoorbeeld Amazon). De klant kan de website verkennen en wanneer hij een product tegenkomt dat hem aanspreekt, kan hij dit in zijn online winkelwagentje plaatsen. Wanneer hij alles wat hij zocht, gevonden heeft, navigeert hij naar de online kassa. Hier wordt het te betalen bedrag van alle geselecteerde producten aangegeven. De klant gaat akkoord en dient het bedrag te betalen. Een prachtig systeem, maar dit proces moet uiteraard beveiligd worden. Stel immers dat iemand, nadat hij akkoord is gegaan met het te betalen bedrag, teruggaat naar het scherm om nog een aantal extra artikelen toe te voegen aan het winkelwagentje. Zo krijgt hij dus deze extra producten gratis. Deze voorbeelden lijken misschien wat ver gezocht, het blijkt echter dat zeer veel webapplicaties te maken hebben met dergelijke aanvallen. De meest voorkomende is forceful browsing waarbij pagina’s worden opgevraagd waartoe de gebruiker geen toegang zou mogen hebben. Zo was er onlangs een grote platenmaatschappij die een “sneak preview”uitbracht, namelijk het eerste liedje van een nieuw album. Het lied was te vinden op een URL die eindigde op /track1.mp3. Het spreekt voor zich dat de andere liedjes te vinden waren op de URL die eindigde op /track2.mp3, /track3.mp3 enzovoort. Zelfs de overheid blijft niet gespaard. De politie van Minnesota ontdekte een tijdje terug dat hun politiebestanden toegankelijk waren voor iedereen die “/personsearch/personsearch.asp ”toevoegde aan ´e´en van de publieke website adressen van het politiedepartement.[13]
1.3
Oplossing
Het doel van deze thesis is tweeledig. Ten eerste dient het verwachte gedrag van clients beschreven te worden. Hiervoor gaan we gebruik maken van Spec Explorer, een ontwikkelingstool die modelgebaseerde specificatie mogelijk maakt. Het verwachte gedrag van de server kan hier ge¨encodeerd worden tot een modelprogramma. Het beschrijven van het verwachte gedrag gebeurt in Spec#. Ten tweede moet er een application-level stateful firewall ontwikkeld worden. Laat ons ter verduidelijking deze termen afzonderlijk behandelen: • Application-level De applicatielaag is de bovenste van de 5 lagen van het tcp/ipreferentiemodel (zie Figuur 1.2)[14], een model om de verschillende lagen van de computernetwerkarchitectuur te beschrijven. Dit is eigenlijk de laag waar de applicaties leven die het netwerk gebruiken. Hierbij valt te denken aan HTTP, FTP, maar ook DNS of NFS. Sommige protocollen draaien zonder dat de gebruiker er erg in heeft (DNS), andere vereisen expliciet netwerkkennis van de gebruiker (smtp). Hier is alle informatie bekend over de doorgestuurde pakketten (terwijl in bijvoorbeeld de netwerklaag enkel 1
HTTP requests worden gebruikt als communicatiemiddel tussen de client en de server.
6
Figuur 1.2: Osi-referentiemodel de ip-adressen van zender en ontvanger van het pakket bekend zijn). Er zal dus gewerkt worden met berichten die betekenis hebben op het niveau van de applicatielaag. • Stateful De toestand van elke verbinding zal worden opgeslagen. De belangrijke attributen van elke verbinding worden bijgehouden. Er is dus steeds kennis over de voorgaande events in een bepaalde volgorde van interactie met de gebruiker (of een andere computer of programma). We willen immers nagaan of een client een aantal (opeenvolgende) acties mag uitvoeren. Daarom gaan we sessies defini¨eren. We gaan per sessie bijhouden welke methodes reeds zijn uitgevoerd en aan de hand daarvan bepalen of een volgende oproep mag uitgevoerd worden. Hier tegenover staat stateless wat betekent dat noch de zender, noch de ontvanger informatie bijhouden over de verzendingen. • Firewall Het doel van een firewall is het onderscheppen en controleren van alle communicatie die binnenkomt en buitengaat uit een lokaal netwerk (of computer). Een firewall zal dienst doen als een poort tussen het lokale netwerk en het internet door gebruik te maken van een opgelegd beveiligingsbeleid. Een firewall kan zowel in een TCP/IP-omgeving als op applicatieniveau gebruikt worden. In een TCP/IP-omgeving zullen pakketten onderzocht worden op basis van hun bron of bestemming. Op applicatieniveau kan een firewall gebruikt worden om de inhoud van de pakketten te filteren, zoals bijvoorbeeld om emailberichten te controleren op bepaalde ”gevaarlijke”woorden. We zullen gebruik maken van de applicatieniveau firewall. Een application-level stateful firewall zal dus de berichten monitoren (onderscheppen) en controleren. De controle zal gebeuren aan de hand van het verwachte gedrag van de client dat gemodelleerd is met Spec Explorer. Er zal nagegaan worden of het bericht mogelijk is in het model, zo niet zal er ingegrepen worden. Dit alles zal gebeuren op applicatieniveau. We zullen dit vanaf nu kortweg firewall noemen. Het is de bedoeling om een generische firewall te cre¨eren. Dit betekent dat het moet mogelijk zijn om zeer eenvoudig deze firewall te gebruiken voor andere client-serverapplicaties. 7
Anderzijds is het ook erg belangrijk dat de server onafhankelijk van de firewall kan blijven. De server moet dus eigenlijk geen weet hebben van de firewall. Zo is het dan mogelijk om de firewall in te pluggen in bestaande applicaties. Net voordat een oproep van de client wordt uitgevoerd aan de serverkant, moet er door de firewall gecontroleerd worden of de oproep voldoet aan de verwachtingen van de server, zo niet dient er een fout op te treden. Om dit mogelijk te maken, moet er net voor de server een interceptor komen die alle oproepen van de client onderschept en doorgeeft aan een filter die zal nagaan of de oproep geldig is. Het is zeker niet de bedoeling om de beveiliging die reeds bestaat aan de clientzijde (inputchecks,...) weg te halen want deze zal in de meeste gevallen wel nuttig zijn. Zo kan er bijvoorbeeld input validatie gebeuren zonder round-trip. Het probleem is echter dat client-side validatie vaak vrij eenvoudig te omzeilen is. Het idee is om bij het bestaande beveiligingssysteem nog een extra veiligheid, de firewall, in te bouwen. Om veilig te zijn voor aanpassingen aan de client-applicatie (door reverse-engineering met tampering), forceful browsing en andere aanvallen, moet deze extra beveiliging zich uiteraard aan de serverkant bevinden. Het is sowieso nuttig om alle functionaliteit aan de serverkant te plaatsen zodat het hacken of reverse-engineeren van de daardoor bekomen thin client weinig gevolgen kan hebben. We onderzoeken twee verschillende soorten van filters. Enerzijds cre¨eren we een filter die het xmlbestand inleest en aan de hand hiervan beslist of een inkomende oproep van de client al dan niet toegelaten is. Vervolgens cre¨eren we een filter die gebruik maakt van een door Spec Explorer gegenereerde dll2 . De filter zal de inkomende oproep doorgeven aan de dll en indien deze niet voldoet aan de vereisten, zal er een fout optreden. Anderzijds laten we Spec Explorer een xmlbestand genereren dat alle toegelaten oproepen bevat. Deze opstelling is grafisch voorgesteld in Figuur 1.3.
1.4
Overzicht van de tekst
Het doel van deze thesis is om te onderzoeken hoe men best kan nagaan of oproepen van de client voldoen aan een vooraf afgesproken protocol. Ik zal nagaan op welke manier men best deze oproepen kan onderscheppen om te onderzoeken en hoe men zal nagaan of de oproep voldoet aan de verwachtingen. Deze verwachtingen zullen steeds beschreven zijn in de Spec Explorertaal, Spec#. Hoofdstuk 2 is dan ook gewijd aan deze ontwikkelingstool en specificatietaal. In Hoofdstuk 3 wordt beschreven waarom Spec# en Spec Explorer nuttig zijn voor deze thesis. Hoofdstuk 4 en Hoofdstuk 5 kunnen beschouwd worden als case studies. Hoofdstuk 4 zal uitleggen hoe men oproepen zal onderscheppen en onderzoeken in .NET Remoting met behulp van channel sinks. Hoofdstuk 5 gaat op gelijkaardige wijze deze interceptie en filtering bekijken voor ASP.NET. In Hoofdstuk 6 worden bemerkingen gegeven bij de gemaakte keuzes en worden alternatieven bekeken. In Hoofdstuk 7 tenslotte worden beide benaderingen vergeleken en worden conclusies getrokken.
2 Een Dynamic Link Library is een bibliotheek met functies, die door meerdere applicaties gebruikt kunnen worden.
8
Figuur 1.3: Client/Servermodel met interceptor en filter
9
Hoofdstuk 2
Spec Explorer Een implementatie is nooit 100% foutenvrij. Het is daarom erg belangrijk om het systeem grondig te testen vooraleer het kan gebruikt worden. Er bestaan reeds verschillende manieren om te testen. Men kan per mogelijk scenario een test case opstellen. Er bestaat ook unittesting, waarbij een bepaald deel van de code grondig wordt getest,... Elk van deze methodes heeft wel zijn nadelen. Zo zal bij het klassieke testen, waarbij de test cases handmatig worden gecre¨eerd, vooral het onderhoud voor problemen zorgen. Bij de minste update moeten immers (bijna) alle test cases worden aangepast. Unit-testing is flexibeler, maar zal slecht ge¨ısoleerde delen onderzoeken. Wanneer het systeem in zijn geheel wordt uitgevoerd, kunnen er onderling nog conflicten voorkomen. Modelgebaseerd testen is een veelbelovende techniek om deze problemen te vermijden. Spec Explorer is een software ontwikkelingstool die gebruik maakt van deze techniek om reactieve systemen te testen. Spec Explorer kan ontwikkelaars helpen om fouten te ontdekken in het ontwerp, de specificatie en implementatie van hun systemen. Het basisidee achter Spec Explorer is enerzijds om het verwachte systeemgedrag (specificatie) om te zetten in uitvoerbare vorm (modelprogramma). Het doel is om dit modelprogramma in parallel uit te voeren met de eigenlijke implementatie en na te gaan of het gedrag van de implementatie ook mogelijk is in het model. Het is zeker niet de bedoeling om in dit hoofdstuk een diepgaande studie te geven van de mogelijkheden van Spec Explorer en modelgebaseerd testen. Daarvoor verwijs ik u door naar een aantal interessante papers over modelgebaseerd testen met Spec Explorer [3], [4] en [5]. Om vergissingen te voorkomen, dient vermeld te worden dat de Spec Explorer Spec# niet mag verward worden met het Spec# Programming System(We zullen dit vanaf nu Spec# noemen). Spec# is een uitbreiding van de objectge¨ori¨enteerde programmeertaal C#. De taal breidt het typesysteem van C# uit met non-null types en checked excepties en er kunnen precondities, postcondities en invarianten toegevoegd worden. Deze taal is zowel geschikt voor runtime checking als statische verificatie. Statische verificatie kan nuttig zijn wanneer men wil controleren of de implementatie voldoet aan een aantal eigenschappen die gedefinieerd zijn als modelinvarianten. Het feit dat beide talen dezelfde naam kregen kan verklaard worden door het feit dat de twee onderzoeksteams die aan de talen werken gedeeltelijk overlappen,en het dus misschien
10
de bedoeling is om op termijn beide talen te unificeren.”Meer informatie over Spec# is te vinden in [6], [7] en [8]. In Sectie 2.1 zullen we bekijken wat reactieve systemen zijn. Sectie 2.2 zal uitgebreid belichten wat modelgebaseerd testen is en hoe de verschillende fasen werken. We sluiten dit hoofdstuk af met Sectie 2.3, het besluit.
2.1
Reactieve systemen
Spec Explorer is een tool om reactieve objectgeori¨enteerde softwaresystemen te testen. Reactieve systemen zijn nondeterministisch. Geen enkele agent op zich (component, thread, ...) controleert alle toestandstransities. Netwerkvertraging, threadscheduling en andere externe factoren kunnen het systeemgedrag be¨ınvloeden. Daarenboven kan de specificatie van een systeem een aantal keuzes openlaten. Deze vrijheid kan ook beschouwd worden als nondeterminisme. Het doel van reactieve systemen is om een voortdurende interactie te onderhouden met de omgeving. Typische voorbeelden van reactieve systemen zijn systemen om het luchtverkeer te controleren, systemen die mechanische toestellen zoals vliegtuigen en treinen sturen en systemen die voortdurend bezig zijn zoals nucleaire reactoren. Spec Explorer gaat om met nondeterminisme door een onderscheid te maken tussen controleerbare acties (controllable actions) die aangeroepen worden door de tester en waarneembare acties (observable actions) die buiten de controle van de tester zijn. Figuur 2.1 geeft een beeld van deze toch wat abstracte begrippen. De figuur omschrijft een (reactief) chatsysteem waar clients kunnen tot toetreden, berichten posten en het systeem verlaten. Dit zijn de controleerbare acties die door de gebruiker kunnen worden opgeroepen. Op ongekende tijdstippen kunnen de observeerbare acties Finished en Received optreden. De gebruiker heeft hier geen controle over. Het chatsysteem wordt verder uitgewerkt in Subsectie 2.2.5. Figuur 2.6 toont een toestandsgrafe van dit systeem. Reactieve systemen kunnen een groot, maar eindig, aantal acties aanbieden. Ze kunnen zelfs een oneindig aantal toestanden hebben, bijvoorbeeld wanneer er dynamisch objecten worden aangemaakt of wanneer de toestand een getal bevat. Spec Explorer ondersteunt hiervoor scenario controle. Dit geeft de mogelijkheid om het aantal toestanden te beperken of te groeperen.
2.2
Modelgebaseerd testen
Modelgebaseerd testen [3] is een veelbelovende techniek om testen eenvoudiger te maken. Testen is immers erg arbeidsintensief en kostelijk. Desalniettemin is het onontbeerlijk. Figuur 2.2 geeft reeds een grafische weergave van de verschillende stappen die in de volgende subsecties in detail bestudeerd zullen worden. Om het idee te verduidelijken zal er een eenvoudig licentieovereenkomst voorbeeld gebruikt worden, dit kan beschouwd worden als een simplistische weergave van een programma dat het mogelijk maakt om een bestand te downloaden op voorwaarde dat men akkoord is gegaan met de licentieovereenkomst. Er dient dus eerst een Accept oproep te komen van de gebruiker vooraleer de Download oproep kan uitgevoerd worden. We onderscheiden 4 fases in het schema te zien in Figuur 2.2:
11
Figuur 2.1: Reactief systeem
Figuur 2.2: Modelgebaseerd testen
12
1. Modellering In de modelleringsfase wordt een (mogelijk oneindig) transitiesysteem gedefinieerd als een programma. 2. Exploratie & Scenario controle Hier zal het transitiesysteem gereduceerd worden tot een eindige testgrafe. 3. Test generatie In deze fase worden de test cases gegenereerd uit de grafe van de vorige fase. Elke test case stelt een bepaald pad in de grafe voor. 4. Test executie De test cases zullen worden uitgevoerd, waarbij het model zal dienst doen als orakel.
2.2.1
Modellering
Een modelprogramma declareert een eindige set van actiemethodes en een set van state variabelen. Precondities van methodes en modelinvarianten defini¨eren condities die al dan niet acties toelaten. Een toestand van het programma wordt bepaald door de waardes van de toestandsvariabelen die voorkomen in het modelprogramma. Een modelprogramma is typisch geschreven in Spec# [6](of in oudere versies van Spec Explorer in AsmL). Een mogelijk modelprogramma voor het licentieovereenkomst voorbeeld zie je hieronder:
We merken op dat elke methode uit een aantal elementen bestaat: • [Action] Dit sleutelwoord gaat elke actiemethode vooraf. Standaard is deze actie controleerbaar. Als we een observeerbare actie willen defini¨eren, dienen we bij het sleutelwoord (Kind=ActionAttributeKind.Observable) toe te voegen. 13
• Actie De hoofding van de methode geeft de naam van de actie, de parameters en het type van de teruggeefwaarde. • Conditie De conditie geeft weer wanneer de methode mag uitgevoerd worden. Als de methode geldig is in de gegeven toestand, zal de methode uitgevoerd worden, anders treedt er een fout op. • Resultaat De body van de methode zal beschrijven wat het resultaat is van de methode. Hier zullen eventueel de toestandsvariabelen worden aangepast zodat het systeem in een nieuwe toestand terecht komt na uitvoering van de methode. Alle methodes vooraf gegaan door het sleutelwoord [Action] zijn dus actiemethodes en kunnen gezien worden als updateregels. Met andere woorden, het zijn functies die, gegeven een toestand en parameters, een nieuwe toestand voortbrengt waarbij sommige toestandsvariabelen ge¨ updatet zijn, op voorwaarde dat de precondities voldaan zijn. In dit voorbeeld is de boolean isAccepted de toestandsvariabele en zal deze bepalen in welke toestand de server zich bevindt. De methode Accept heeft geen precondities (requires true is steeds voldaan) en kan dus altijd uitgevoerd worden. De methode Download kan pas opgeroepen worden nadat de methode Accept is opgeroepen. Wanneer de methode Download wordt uitgevoerd, wordt de toestandsvariabele isAccepted terug op false gezet. Het accepteren van de overeenkomst geeft recht op 1 maal downloaden van het bestand. De licentieovereenkomst moet dus geaccepteerd zijn, zodat isAccepted de waarde true heeft, voordat het bestand mag teruggegeven worden. (Voor de eenvoudigheid wordt er niets teruggegeven door Download.) Beide methodes kunnen opgeroepen worden door de tester en zijn dus controleerbaar. Met deze code is het echter enkel mogelijk om ´e´en client te controleren op het accepteren van de licentieovereenkomst omdat we slechts 1 globale variabele hebben. We willen het uiteraard mogelijk maken om meerdere clients te verifi¨eren. Dit kan gebeuren door de code lichtjes te wijzigen zoals hieronder. namespace Model{ public class Client{} Set
clients = Set{}; Set isAccepted = Set{}; [Action] public Client CreateClient() { let c = new Client(); clients += Set{c}; return c; }
[Action] 14
public void Accept(Client c) requires c in clients; { isAccepted +=Set{c}; } [Action] public void Download(Client c) requires c in isAccepted; { isAccepted -=Set{c}; } } We merken dat er een methode is bijgekomen. CreateClient gaat bij elke oproep een nieuwe client aanmaken en teruggeven. Zo kan er met meerdere clients gewerkt worden. Om te weten over welke client het gaat, moet deze uiteraard aan elke methode meegegeven worden als parameter. We kunnen nu ook niet meer werken met ´e´en globale bool IsAccepted aangezien we werken met meerdere clients. Daarom gaan we werken met sets, ingebouwde datatypes van Spec#. Een set is niet meer dan een verzameling. We werken met 2 Sets: de set clients waarin alle clients zitten die aangemaakt zijn door de methode CreateClient en de set IsAccepted waarin alle clients zitten die de licentieovereenkomst geaccepteerd hebben. Dit wijzigt de precondities lichtjes. Zo krijgt de methode Accept een preconditie die eist dat de client die meegegeven wordt reeds aangemaakt is en dus tot de set clients behoort. Bij de methode Download moet niet langer de boolean IsAccepted true zijn, maar moet de meegegeven client behoren tot de set IsAccepted. De 2 sets zijn in dit programma de toestandsvariabelen en gaan bepalen in welke toestand de server zich bevindt.
2.2.2
Exploratie & Scenario controle
Spec Explorer gebruikt een toestandsexploratiealgoritme om representatief gedrag uit het model te extraheren. De bedoeling is om een grafe te genereren zoals te zien in Figuur 2.3 en Figuur 2.4 In een gegeven modeltoestand (vertrekkende van de initi¨ele toestand die gegeven is door de initi¨ele waarden van de toestandsvariabelen) worden de invocaties bepaald actie/parameter combinaties - die toegelaten zijn door hun precondities in die toestand. Hun opvolgende toestanden worden berekend voor elke invocatie. Dit wordt herhaald tot er geen toestanden en invocaties meer over zijn om te verkennen. De parameters die gebruikt worden voor de invocaties worden verstrekt door parametergeneratoren die toestandsonafhankelijk zijn. Standaardgeneratoren worden automatisch geselecteerd (bijvoorbeeld voor objecten levert de generator de enumof(T) collectie, die bestaat uit oneindig veel elementen van de klasse T). Buiten de keuze van parameters kan de exploratie beperkt worden door verscheidene andere scenario controle technieken. Scenario controle is in de meeste gevallen onontbeerlijk. Vaak zal een modelprogramma leiden tot een oneindig aantal toestanden. Ook ons eenvoudig licentieovereenkomst voorbeeld met meerdere clients heeft zonder scenario controle oneindig veel toestanden. CreateClient kan oneindig vaak worden opgeroepen en voor elke oproep wordt er een nieuwe toestand gemaakt. 15
Aangezien scenario controle erg belangrijk is voor de exploratiefase, volgen hieronder de technieken aangeboden door Spec Explorer. Parameterselectie In plaats van te vertrouwen op de standaard parametergeneratoren, kan je zelf ook een set van parameters defini¨eren waar je gebruik van wil maken. Bijvoorbeeld, een methode heeft een integer als parameter. Als je als tester weet dat deze integer enkel de waardes 1, 2 of 3 heeft, is het helemaal niet nodig om een oneindige verzameling integers te overlopen, maar kan je de verzameling beperken tot Set{1,2,3}. Toestandfilters Filters beperken het aantal toestanden die zullen verkend worden. Dit is belangrijk voor modelprogramma’s die geen bovengrens hebben op het aantal toestanden. Dit is zeker zo bij een verzameling objecten zoals in het licentieovereenkomst voorbeeld met meerdere clients. Zonder filter zal er een oneindige verzameling gegenereerd worden wat uiteraard leidt tot een oneindig aantal toestanden. Om dit te verhinderen gaan we het aantal elementen in de verzameling beperken. Als we het aantal clients willen beperken tot 2, zal de filter er zo uitzien: enumof(Client).Size<3. Toestanden die niet aan deze conditie voldoen, worden weggegooid en dus niet verder verkend. Toestandsgroepering Standaard worden alle toestanden bekeken. Het is ook mogelijk om te zoeken naar representatieve voorbeelden van toestanden die gemeenschappelijke kenmerken delen. Toestandsgroepering (state grouping) zorgt ervoor dat de exploratie effici¨enter gebeurt door de toestanden die gelijkaardig zijn aan reeds verkende toestanden uit te sluiten. Toestanden zijn gelijkaardig als de evaluatie van de door de gebruiker gedefinieerde uitdrukkingen identiek is. Bijvoorbeeld, als een toestand een stapel (stack) bevat die een opeenvolging is van integers dan kan de uitdrukking stack.Length gebruikt worden om aan te geven dat twee toestanden met een stapellengte van 2 onderling verwisselbaar zijn tijdens de exploratie. Het resultaat van de exploratie van het eerste licentieovereenkomst voorbeeld is te zien in Figuur 2.3. Hier was geen scenario controle nodig. De testgrafe van het licentieovereenkomst voorbeeld met meerdere clients is weergegeven in Figuur 2.4. Hier moest het aantal clients beperkt worden. In Figuur 2.4 zie je het resultaat met 1 client, in Figuur 2.5 het resultaat met 2 clients.
2.2.3
Test generatie
We vertrekken van de eindige toestandsmachine (abstract state machine)1 . Uit deze toestandsmachine willen we test cases genereren. Tijdens de test generatie zal telkens een sequentie van acties gekozen worden. Zo’n sequentie vormt een test case. Modelgebaseerde test generatie en executie zijn twee sterk gerelateerde processen. In het ene uiterste, het traditionele idee van test generatie, worden testen op voorhand gegenereerd 1
De eindige toestandsmachine is niet meer dan de grafe die gegenereerd is in de exploratie fase.
16
Figuur 2.3: Eenvoudige licentieovereenkomst voorbeeld
Figuur 2.4: licentieovereenkomst met meerdere clients; filter: enumof(Client).Size<2
Figuur 2.5: licentieovereenkomst met meerdere clients; filter: enumof(Client).Size<3
17
op basis van een gegeven specificatie of model waarbij het doel van de gegenereerde testen verschillend kan zijn. Het kan de bedoeling zijn om een soort van dekking te hebben van de toestandsruimte, om een toestand te bereiken die voldoet aan een bepaalde eigenschap of om willekeurige wandelingen (random walks) te genereren in de toestandsruimte. Dit wordt offline testing genoemd aangezien de test executie een secundair proces is dat gebruik maakt van de voorgegenereerde testen en deze uitvoert tegen de implementatie die getest wordt om conflicten te vinden tussen het gedrag van het systeem en het verwachte gedrag. In het andere uiterste zijn beide processen samengesmolten tot ´e´en proces waarbij testen on-the-fly gegenereerd worden. Hierbij wordt er geen toestandsmachine op voorhand gegenereerd. Er wordt begonnen bij de begintoestand. Daar wordt dan on-the-fly gekeken wat op dat moment mogelijk is en zo komt men in de volgende toestand terecht waar men opnieuw zal nagaan welke acties hier mogelijk zijn. De test cases worden dus dynamisch gecre¨eerd. Dit wordt online testing of on-the-fly testing genoemd. Dit is een manier om om te gaan met het nondeterminisme van reactieve systemen. Wanneer Spec Explorer test in zijn on-the-fly modus, zal de exploratie gebeuren op basis van de geschiedenis van de test run. Dit laat toe om de niet-deterministische takken die niet verkend waren door de implementatie tijdens de test run, weg te laten. Voor bepaalde traversal strategie¨en 2 , zoals die om een willekeurige wandeling te genereren, waar een willekeurig pad wordt afgegaan op zoek naar inconsistenties tussen het model en de implementatie, zijn de testen gelimiteerd door een maximum aantal stappen. Eens het maximum aantal bereikt is, zal de test verder gaan met het kortste pad tot aan een aanvaardbare toestand. Als het testdoel transitiedekking is, kan het mogelijk zijn om een aantal testsegments opnieuw uit te voeren om verschillende observeerbare acties waar te nemen van eenzelfde onderliggende toestand.
2.2.4
Test executie
Hier wordt nagegaan of het gedrag van het systeem conform is met het verwachte gedrag. We willen de acties van het systeem en het modelprogramma vergelijken. Spec Explorer weet echter niet welke methodes van de implementatie overeenkomen met welke methodes van de specificatie. Er moeten dus conformiteitsbindingen gemaakt worden. Dit zijn isomorfe mappings tussen de signatuur van het modelprogramma en de implementatie die getest wordt. Tijdens executie maakt de implementatie callbacks via deze bindingen. Een typisch scenario is dat een controleerbare actie een thread start in de implementatie waaruit verschillende observeerbare acties (callbacks) kunnen volgen. Als een test faalt, kan de fout zowel bij de implementatie als de specificatie te vinden zijn. In principe wordt verondersteld dat de specificatie correct werkt om hiermee de implementatie te testen (dit is immers het doel van modelgebaseerd testen). Onderzoek wijst echter uit dat in de helft van de gevallen de fout in de specificatie te vinden is[5]. Als men gebruik wil maken van modelgebaseerd testen moet men dus zeker opletten voor foute specificaties.
2.2.5
Uitgewerkt voorbeeld
Aangezien het licentieovereenkomst voorbeeld nogal eenvoudig is, wil ik de sectie over modelgebaseerd testen afsluiten met een wat uitgebreider voorbeeld. Zo worden ook de nieuwe 2
Traversal strategie¨en zijn strategie¨en die de test suites (verzameling test cases) genereren
18
termen nog wat meer verduidelijkt. We gaan werken met een reactief systeem (zowel controleerbare als observeerbare acties). Als voorbeeld bekijken we een chatsysteem, zoals beschreven in [5]. Het systeem biedt de mogelijkheid aan clients om toe te treden tot de chatsessie of deze te verlaten. Clients die toegetreden zijn, worden leden genoemd en mogen berichten posten. Het systeem mag het huidige bericht doorsturen aan de leden in gelijk welke volgorde. De specificatie schrijft wel voor dat alle leden het huidige bericht moeten ontvangen hebben vooraleer een lid het volgende bericht in de lijst mag ontvangen. Het modelprogramma ziet er uit als volgt: class Client {} //toestandsvariabelen static Set members = Set{}; static Set nonmembers = Set{}; static Seq<string> messages = Seq{}; static Set recipients =Set{}; //controleerbare acties [Action] static Client Create() requires true; { Client c = new Client(); nonmembers += Set{c}; return c; } [Action] static void Enter(Client c) requires c in nonmembers; { nonmembers -= Set{c}; members += Set{c}; } [Action] static void Exit(Client c) requires c in members; { members -= Set{c}; nonmembers += Set{c}; } [Action] static void Post(Client c, string content) requires c in members; { 19
if (messages.IsEmpty) recipients = members; messages +=Seq{content}; } //observeerbare acties [Action(Kind=ActionAttributeKind.Observable)] static void Received(Client c, string msg) requires !messages.IsEmpty && msg == messages.Head; requires c in recipients; { recipients -= Set{c}; } [Action(Kind=ActionAttributeKind.Observable)] static void Finished(string msg) requires !messages.IsEmpty && msg==messages.Head; requires (recipients * members).IsEmpty; { messages = messages.Tail; recipients = messages.IsEmpty ? Set{} : members; } De toestand wordt bepaald door het tupel <members, nonmembers, messages, recipients>. Er zijn vier controleerbare acties: • Create zorgt voor het aanmaken van clients. Dit is steeds mogelijk (requires true). Een nieuwe client wordt initieel toegevoegd aan de set van nonmembers. • Enter laat een client die nog geen member is toetreden tot de sessie. De variabelen nonmembers en members geven het veranderen van toestand weer. • Exit laat een lid de sessie verlaten. • Post laat een lid een nieuw bericht posten dat door iedereen die lid is van de sessie dient ontvangen te worden. De toestand wordt ge¨ updatet door het nieuwe bericht achteraan de lijst van te ontvangen berichten toe te voegen. Als de lijst leeg is, zal het nieuwe bericht het huidige bericht worden en zullen de ontvangers (recipients) de huidige leden worden. Er zijn ook twee observeerbare acties, namelijk: • Received kan enkel waargenomen worden wanneer het systeem een bericht doorstuurt naar een client die behoort tot de set van verwachte ontvangers. Dit kan gezien worden als een callback wanneer het systeem een bepaald bericht doorstuurt naar een bepaalde client. Wanneer dit geobserveerd wordt, zal de toestand ge¨ updatet worden door de client te verwijderen uit de set van ontvangers. Dit zorgt ervoor dat de berichten slechts ´e´enmaal per ontvanger worden geleverd. 20
Figuur 2.6: Chatsysteem; filter: enumof(Client).Size<2, messages.Length<2 • Finished kan waargenomen worden wanneer er geen ontvangers meer over zijn (die de chatsessie nog niet verlaten hebben) voor het huidige bericht. Met recipients * members wordt dus de doorsnede tussen de ontvangers en leden bedoeld. Er zal dan overgegaan worden naar het volgende bericht in de lijst om te versturen. Voor exploratie van dit model is het nodig om scenario controle toe te passen. We dienen het aantal clients te beperken. Ook gaan we de verzonden berichten beperken tot Set{“hello”}, met andere woorden zal enkel “hello” als bericht worden verstuurd. De berichten beperken tot ´e´en mogelijkheid is echter niet genoeg, er kunnen immers oneindig veel “hello”berichten terechtkomen in de berichtenlijst. Daarom gaan we de lengte hiervan ook beperken. De grafe in Figuur 2.6 geeft het chatsysteem met 1 client en maximum 1 bericht in de lijst weer. De cirkels duiden de stabiele toestanden aan, dit zijn de toestanden waar enkel controleerbare acties mogelijk zijn. De ruiten daarentegen zijn de niet-stabiele toestanden, hier is minstens ´e´en observeerbare actie mogelijk. Om aan te tonen hoe snel zo’n grafe groeit, zie je in Figuur 2.7 het chatsysteem met nog steeds 1 client maar met maximum 3 berichten in de lijst.
2.2.6
Toestandsinvarianten
Het is ook mogelijk dat het modelprogramma toestandsinvarianten (state invariant) bevat. Een toestandsinvariant is gelijk aan een booleaanse expressie die moet gelden in gelijk welke toestand. Het modelprogramma schendt een invariant als deze evalueert tot false in een bepaalde toestand. In dat geval is het modelprogramma niet geldig. Een invariant kan dus gezien worden als een veiligheidsvoorwaarde voor de actiemethodes. Als er een fout zit in 21
Figuur 2.7: Chatsysteem; filter: enumof(Client).Size<2, messages.Length<4 een methode, zal dit gedetecteerd worden door de invarianten aangezien deze nu tot false zal evalueren. Zo kunnen we voor het chatsysteem van vorige sectie als invariant Forall{ c in nonmembers; c notin members} toevoegen aan het modelprogramma. Dit bepaalt dat een client die een lid is niet tot de verzameling van niet-leden mag behoren.
2.3
Besluit
Dit hoofdstuk heeft de basisconcepten van Spec Explorer en modelgebaseerd testen besproken. In de modelleringsfase wordt een (mogelijk oneindig) transitiesysteem gedefinieerd als een programma. Tijdens de exploratiefase zal dit transitiesysteem gereduceerd worden tot een eindige toestandsmachine. Voor deze reductie worden vaak scenario controle technieken zoals parameterselectie, toestandsfiltering en toestandsgroepering toegepast. Vervolgens worden de test cases gegenereerd in de test generatie fase. Tot slot kunnen deze test cases uitgevoerd worden om de implementatie te testen. Het is duidelijk geworden dat modelgebaseerd testen een veelbelovende techniek is. Niet alleen is er maar 1 programma meer dat moet onderhouden worden waaruit automatisch de nodige test cases worden gegenereerd (terwijl bij het klassieke testen er veel test cases moeten up-to-date gehouden worden), ook lost modelgebaseerd testen het probleem van unit-testing op. Bij unit-testing worden componenten ge¨ısoleerd getest, terwijl bij modelgebaseerd testen steeds het geheel wordt bekeken zodat ook de problemen die ontstaan door interactie tussen de verschillende componenten ontdekt worden tijdens het testproces. In deze thesis is Spec Explorer echter niet gebruikt als modelgebaseerde testingtool. In het volgende hoofdstuk zal uitgebreid belicht worden hoe Spec Explorer kan gebruikt worden om
22
het verwachte gedrag van een client te specificeren in een modelprogramma. Dit programma zal dan, in dll- of xmlvorm door de filter kunnen gebruikt worden om na te gaan of een bepaalde oproep is toegelaten.
23
Hoofdstuk 3
Filteren met Spec Explorer Zoals reeds bleek uit de inleiding handelt deze thesis niet over modelgebaseerd testen, maar over het onderscheppen en controleren van communicatie tussen clients en server. In dit hoofdstuk wordt besproken waarom we dan toch gebruik maken van Spec Explorer. Er bestaan verschillende technologie¨en om client-serverapplicaties te bouwen. Afhankelijk van welke technologie gebruikt wordt, zullen er verschillende onderscheppingsmechanismen kunnen worden toegepast. In de volgende hoofdstukken zullen we hier 2 manieren van bespreken. Omdat er verschillende onderscheppingmethoden mogelijk zijn, dienen we een interface, IFilter, te cre¨eren om zo de controle-component onafhankelijk te maken van de interceptiecomponent en vice versa. In sectie Sectie 3.1 wordt bekeken waarom Spec Explorer gebruikt wordt. Sectie Sectie 3.2 vertelt hoe Spec Explorer effectief gebruikt wordt en hoe de verschillende filters werken.
3.1
Relevantie van Spec Explorer
We willen zeker zijn dat een client zich gedraagt zoals verwacht. We gaan hiervoor een application-level stateful firewall toevoegen aan het client-serversysteem. De firewall kan opgesplitst worden in 2 delen: de interceptor en de filter. De mogelijke manieren van interceptie worden in de volgende hoofdstukken uitvoerig besproken. In dit hoofdstuk zal bepaald worden hoe de filter zal werken. In de filter willen we nagaan of de binnenkomende oproepen mogen uitgevoerd worden. We dienen dus het verwachte gedrag op een bepaalde manier te expliciteren. Dit kan gebeuren door het verwachte gedrag te beschrijven in Spec Explorer modelprogramma’s. De filter zal dus van de interceptor de onderschepte oproepen binnen krijgen en zal aan de hand van het modelprogramma beslissen of de methode mag uitgevoerd worden. Kortom, we moeten dus eerst een model maken waarin het verwachte gedrag van de client wordt gespecificeerd. Vervolgens zat dit gedrag at-runtime gecheckt worden ten opzichte van het model. Deze check gebeurt door de application-level stateful firewall.
3.2
Filters
In Spec Explorer komt een actie overeen met een methode uit de implementatie. Spec Explorer beschouwt methodes als atomair. Wij willen echter het onderscheid maken tussen het
24
Figuur 3.1: Diagramma van de filters starten van een oproep en het eindigen van een oproep, dit om het mogelijk te maken callbacks te modelleren en om terugkeerwaarden te onderscheppen. De volledige informatie over de oproep is immers pas bekend bij het terugkeren van de methode. In deze thesis beperken we ons tot het laatste. Een actie zal dus moeten gelijkgesteld worden aan een methode-ingang of methode-terugkeer. Dit is immers het meest algemene. Wanneer het modelprogramma geschreven is, moet dit nog aangesproken kunnen worden om te controleren of het gedrag van de client overeenkomt met de verwachtingen. We zullen een filter implementeren die opgeroepen wordt door de interceptor bij elke onderschepte oproep. De interceptor zal de nodige informatie doorgeven aan de filter en de filter zal met behulp van het modelprogramma nagaan of de oproep mag uitgevoerd worden. Er is een filter per remote object. Dit is reeds grafisch voorgesteld in Figuur 1.1. Hoe de interceptor weet welke filter moet aangesproken worden, zal besproken worden in de volgende hoofdstukken. Er zijn meerdere mogelijkheden om het modelprogramma aan te roepen. Enerzijds kan het modelprogramma gecompileerd zijn tot dll en rechtstreeks worden aangesproken. Anderzijds kan het modelprogramma ge¨exploreerd worden tot testgrafe waarbij alle toestanden en transities kunnen ge¨exporteerd worden in een xml-bestand. We gaan beide mogelijkheden onderzoeken. Beide filters zullen de interface IFilter implementeren, zoals te zien is in Figuur 3.1. De interface zal ervoor zorgen dat de interceptie-component en controle-component onafhankelijk van elkaar kunnen ontwikkeld worden. Dit is immers nodig aangezien er verschillende onderscheppingsmechanismen bestaan. We onderscheiden 2 methodes: • StartCall(String methodName, Object[] parameters) Deze methode zal door de interceptor worden aangeroepen telkens er een nieuwe oproep onderschept wordt. De methode zal nagaan of het modelprogramma de methode toelaat gegeven de huidige toestand en de gegeven parameters. Indien niet zal deze methode een exceptie gooien. • ReturnCall(String methodName, Object returnvalue) Deze methode zal door de interceptor worden opgeroepen wanneer de methode terugkeert naar de client. Deze methode zal gebruikt worden wanneer het modelprogramma de teruggegeven waarde nodig heeft om de huidige toestand te updaten. Vooraleer we bekijken hoe de filter omgaat met de dll-versie van het modelprogramma en hoe de oproepen gefilterd worden wanneer met het modelprogramma in xml-vorm wordt 25
gewerkt, gaan we bekijken hoe een bijhorend modelprogramma er moet uit zien en aan welke voorwaarden deze moet voldoen.
3.2.1
Modelprogramma’s
Het is de bedoeling om een firewall te cre¨eren die kan gebruikt worden voor gelijk welke clientserverapplicatie zonder dat de code moet aangepast worden. Voorlopig dienen er een aantal afspraken hier omtrent gemaakt te worden. Het zou echter ook mogelijk zijn om deze afspraken te versoepelen. Dit kan door gebruik te maken van een xmlbestand waar alle waarden worden ingevoerd. Zo kunnen de objecten dan met behulp van reflectie worden aangemaakt. In deze versie zijn echter expliciete afspraken nodig. Zo dient elk modelprogramma te behoren tot de namespace FilterModel en zal de klassenaam Model moeten zijn. Wanneer het modelprogramma ge¨exporteerd wordt als xml, dient dit bestand filterModel.xml te noemen. De dll dient filterModel.dll te noemen. Omdat alle filters dezelfde dll (en dus dezelfde klasse) aanspreken, is het nodig om het modelprogramma toegankelijk te maken voor meerdere clients (zoals het licentieovereenkomst voorbeeld in Sectie 2.2 en het uitgewerkte voorbeeld dat besproken is in Subsectie 2.2.5). De create-methode zal CreateServiceClassObj noemen en het type dat teruggegeven wordt, is ServiceClassObj. Het is in principe wel mogelijk om meerdere dll’s aan te spreken, maar omwille van performantieredenen, is er geopteerd om dit niet te doen. Deze mogelijkheid wordt uitgebreider besproken in Hoofdstuk 6. Het schrijven van modelprogramma’s is niet altijd voor de hand liggend. Als voorbeeld bekijken we een programma dat de toegang tot een (gedeeld) bestand, een directory of een databank kan bepalen. De client dient eerst toegang te vragen tot het bestand (Open()). Deze oproep geeft terug welke bewerkingen de client hierop mag uitvoeren - lezen (Read()) en/of schrijven (Write()) of niets. Afhankelijk van de teruggeven waarde moeten de acties van de client beperkt worden tot diegene die toegewezen zijn. Als de client het bestand niet langer nodig heeft, kan deze zijn acties terug vrijgeven (Close()). In dit geval is het niet voldoende om enkel de oproepen te onderscheppen, het modelprogramma moet immers ook te weten kunnen komen welke acties de client mag uitvoeren. Daarom moet ook de methode worden onderschept bij terugkeer om de teruggegeven waarde te kunnen gebruiken. Indien ook de teruggegeven waarden nodig zijn in het modelprogramma, dienen de nodige actiemethodes in het modelprogramma dezelfde naam te krijgen als de overeenkomstige methode in de implementatie in de server, maar dan voorafgegaan door Begin en End. We zullen dit illustreren met de code van het modelprogramma dat nagaat of een client een bepaalde actie mag uitvoeren op een bestand. namespace FilterModel { public class ServiceClassObj{} public class Model{
static Set <ServiceClassObj> closed = Set{}; 26
static static static static
Set Set Set Set
<ServiceClassObj> <ServiceClassObj> <ServiceClassObj> <ServiceClassObj>
read = Set{}; write = Set{}; readWrite = Set{}; in_progress = Set{};
[Action] public static ServiceClassObj CreateServiceClassObj() { let sco = new ServiceClassObj(); closed += Set{sco}; return sco; } [Action] public static void BeginOpen(ServiceClassObj sco) requires sco in closed; { closed-=Set{sco}; in_progress+=Set{sco}; } [Action] public static void EndOpen(ServiceClassObj sco, String newState) requires sco in in_progress; { if(newState.Equals("ReadWrite")) { readWrite+=Set{sco}; } else { if(newState.Equals("Read")) { read+=Set{sco}; } else { if(newState.Equals("Write")) { write+=Set{sco}; } else { closed+=Set{sco}; } } } 27
in_progress-=Set{sco}; }
[Action] public static String Read(ServiceClassObj sco) requires sco in read || sco in readWrite; { return "Read"; } [Action] public static String Write(ServiceClassObj sco) requires sco in write || sco in readWrite; { return "Write"; } [Action] public static String Close(ServiceClassObj sco) requires sco in read || sco in write || sco in readWrite; { if(sco in read) { read-=Set{sco}; } else { if(sco in write) { write-=Set{sco}; } else { readWrite-=Set{sco}; } } closed+=Set{sco}; return "Closed"; } } Een mogelijk eindige toestandsmachine is weergegeven in Figuur 3.2. Het aantal ServiceClassObj’s is beperkt tot 1. Bij het aanmaken van een filter, zal CreateServiceClassObj worden opgeroepen. Dit object komt dan in de closed set terecht. Aangezien elke client een eigen filter zal toegewezen 28
Figuur 3.2: Toestandsgrafe van directory-toegang voorbeeld met 1 ServiceClassObj krijgen, kan de filter het teruggegeven ServiceClassObj bijhouden. Wanneer een client de methode Open() oproept (dit kan enkel als het ServiceClassObj object tot de set Closed behoort), zal de filter de methode BeginOpen() oproepen en zal, indien de methode toegelaten wordt, de methode bij het terugkeren onderschept worden waardoor EndOpen() zal opgeroepen worden. Hier zal gekeken worden welke waarde de server teruggegeven heeft en afhankelijk hiervan zal het ServiceClassObj in de overeenkomstige set terecht komen. Bij de methodes Read() en Write() wordt gecheckt of de client deze actie mag uitvoeren. Bij de Close() oproep wordt het ServiceClassObj uit de set gehaald waar deze is aan toegevoegd door de EndOpen methode. Het spreekt voor zich dat de implementatie van de server geen methode mag hebben met eenzelfde signatuur als een reeds gebruikte methode, vooraf gegaan door Begin of End. Het kan immers best zijn dat het bijhorende modelprogramma die bepaalde methode moet opsplitsen zodat er 2 methodes met dezelfde naam zouden kunnen voorkomen. Dit moet uiteraard vermeden worden.
3.2.2
XMLFilter
Het is mogelijk om het modelprogramma te exporteren als XML. In dit xmlbestand staan de mogelijke toestanden en transities gedefinieerd die weergegeven zijn in de overeenkomstige eindige toestandsmachine. Er dient dus eerst een grafe gegenereerd te worden. Hieruit kan het xmlbestand dan gecre¨eerd worden. De filter zal dit bestand inlezen en omzetten in een opzoektabel. Een voorbeeld van zo’n xmlbestand voor het licentieovereenkomst voorbeeld met meerdere clients uit Hoofdstuk 2 met dezelfde scenario controlefilters als gebruikt voor Figuur 2.4 zie je hieronder. Het xmlbestand is dus niets meer dan de beschrijving van de gegenereerde grafe. Om correct gebruikt te worden, dient men minstens de transities te exporteren met verbose aangevinkt. <source>S0 Model.CreateClient c0 S1 29
<source>S1 Model.Accept <param0>c0 S2 <source>S2 Model.Download <param0>c0 S1 <source>S2 Model.Accept <param0>c0 S2 We merken dat een transitie bestaat uit: • transition id Dit is het unieke herkenningsteken (identifier) van de transitie • source Dit is de begintoestand van de actiemethode • invocation id Dit is het unieke invocatieherkenningsteken • invocation action kind Geeft het type van de actie weer • invocation action Geeft de naam van de actiemethode (vooraf gegaan door de klassenaam) • invocation param# met # een getal beginnend vanaf 0, geeft de parameters weer (indien de methode parameters heeft) • result id heeft het unieke herkenningsteken van de teruggegeven waarde • result geeft het resultaat weer (indien de methode niet void is) • target geeft de eindtoestand (de toestand na het uitvoeren van de methode) 30
Een oproep zal pas toegelaten worden wanneer de filter zich in de toestand bevindt die overeenkomt met de verwachte begintoestand van de methode in het xmlbestand en wanneer de parameters overeenkomen. Als alles geldig is, zal de toestand van de filter veranderd worden in de eindtoestand van de opgeroepen methode in het xmlbestand. Zo niet, zal er een exceptie gegooid worden. Aangezien een xmlbestand telkens opnieuw kan ingelezen en verwerkt worden, is het mogelijk om te werken met een modelprogramma voor 1 client. In principe moet het modelprogramma hier dus niet voor meerdere clients gemaakt zijn. Om consistentieredenen (bij de dllFilter die wordt beschreven in Subsectie 3.2.3 is dit echter wel nodig) is er echter gekozen om ook hier met een modelprogramma voor meerdere clients te werken. Zo kan eenzelfde modelprogramma voor beide filters worden gebruikt. Als we kijken naar het voorbeeldbestand, valt het op dat er als parameter c0 wordt gebruikt. Dit duidt meteen op het probleem van objectreferenties als parameter. Het is immers logisch dat een xmlbestand enkel tekst bevat en dus geen echte referenties naar objecten kan bevatten. Voor het doorgeven van de client is dit echter geen probleem. Aangezien er per filter slechts 1 client is, kan de filter de teruggegeven waarde bij het aanmaken van de client bewaren en gebruiken indien nodig. De xmlFilter zal telkens de variabele, die het ServiceClassObj object voorstelt, vooraan toevoegen in de rij van parameters. In het geval van het licentieovereenkomst voorbeeld, zal dus telkens de string c0 worden toegevoegd. Behalve het doorgeven van de client kan er dus bij de xmlFilter niet met objectreferenties gewerkt worden en kunnen methodes enkel parameters van het primitieve type hebben. Dit is een zware beperking. Een andere beperking die echter onontbeerlijk is bij de xmlFilter is de scenario controle. Het xmlbestand kan pas ge¨exporteerd worden na de exploratiefase (wanneer de grafe gegenereerd is). Om de grafe te genereren is er in de meeste gevallen scenario controle nodig geweest om het aantal mogelijkheden binnen de perken te houden. Het xmlbestand zal dus (bijna) nooit bestaan uit alle mogelijkheden. Dit is uiteraard logisch, anders zou ook het xmlbestand dat ge¨exporteerd wordt uit de grafe oneindig lang zijn. Er dient dus grondig nagedacht te worden welke mogelijkheden er zullen gegenereerd worden, enkel deze mogelijkheden zullen geaccepteerd worden door de filter. Indien men bijvoorbeeld in het modelprogramma gaat controleren op een integer, is het belangrijk om te weten wat de mogelijke waarden zullen zijn. Rond dit probleem bestaat enige discussie. Sommigen beweren dat er nooit oneindig veel mogelijkheden zullen zijn aangezien er steeds wel een grens te bepalen is voor de waardes van een bepaald probleem (ook al levert dit een zeer groot interval op). Anderen gaan hier niet mee akkoord en zijn ervan overtuigd dat in sommige omstandigheden er toch oneindig veel mogelijkheden moeten zijn (bijvoorbeeld wanneer men een bod kan doen is het nodig om gelijk welk bedrag te kunnen bieden en kunnen hier geen bovengrenzen op gelegd worden). Ik geloof dat, in een aantal gevallen waar er oneindig veel mogelijkheden zijn, het probleem kan herleid worden tot een eindig aantal mogelijkheden. In het geval van het bieden op een voorwerp veronderstel ik dat er toch nog ergens een bovengrens (en ondergrens) kan gekozen worden (misschien kunnen de voorwerpen waarop men kan bieden wel in verschillende prijscategorie¨en geplaatst worden). In sommige gevallen zal het echter onmogelijk zijn om de oneindig veel mogelijkheden te beperken tot een eindig aantal. 31
Figuur 3.3: Diagramma van de xmlFilter Jammer genoeg zal het beperken tot een eindig aantal niet altijd voldoende zijn voor de xmlFilter. Als men bijvoorbeeld een oneindig aantal herleidt tot 1 miljoen, zal de xmlFilter dit nog steeds niet kunnen verwerken. Zelfs als het aantal mogelijkheden zeer beperkt is, zoals de 52 mogelijkheden bij een kaartspel, kan een opeenvolging van keuzes leiden tot een combinatorische explosie die niet kan behandeld worden door de xmlFilter. Een grafische weergave van de xmlFilter is te zien in Figuur 3.3. De code van de xmlFilter is weergegeven in Appendix A. Bij het aanmaken van een xmlFilter, wordt aan de StateTransitionTable (een singleton-klasse) de transities gevraagd. De StateTransitionTable zal met behulp van de XMLReader klasse het xmlbestand inlezen en omzetten in een ArrayList van StateTransitions. De StateTransition klasse is ook weergegeven in Figuur 3.3. De StateTransitionTable klasse zorgt enkel voor het inlezen van xml, deze zal dus niet verder besproken worden. De currentState wordt ge¨ınitialiseerd op "S0", de begintoestand. De naam van het xmlbestand is de constante ”filterModel.xml”, zoals reeds vermeld in Subsectie 3.2.1. Vervolgens wordt de Initialize methode opgeroepen waarin het ServiceClassObj object wordt aangemaakt. Bij het oproepen van StartCall wordt het ServiceClassObj object als eerste element toegevoegd aan de rij van parameters. Vervolgens wordt gecontroleerd of de methode met de aangepaste parameterrij geldig is in de huidige toestand. Indien dit niet zo is, wordt opnieuw geprobeerd met dezelfde parameters maar met de methodenaam vooraf gegaan door Begin. Indien dit geldig is, wordt in progress op true gezet, en zal de terugkeerwaarde worden onderzocht wanneer ReturnCall wordt opgeroepen. Bij de ReturnCall methode wordt dus enkel de teruggegeven waarde onderzocht indien bij StartCall en Begin methode is opgeroepen, dit ook weer gelijkaardig aan de dllFilter. Merk op dat hier telkens de toestand expliciet moet ge¨ updatet worden. Telkens een actiemethode wordt uitgevoerd, moet de huidige toestand veranderd worden in de targettoestand van de methode.
32
Figuur 3.4: Diagramma van de dllFilter
3.2.3
DllFilter
Dll staat voor Dynamic Link Library, een bibliotheek van uitvoerbare functies of data die kunnen gebruikt worden door een Windows applicatie. Het modelprogramma kan gecompileerd worden tot zo’n bibliotheek. Deze kan dan aangeroepen worden door de filter. De dll zal dan on-the-fly verkend worden (zoals beschreven in Subsectie 2.2.3). In Figuur 3.4 zien we het diagramma van de dllFilter. De implementatie van de dllFilter is erg gelijkaardig aan de xmlFilter. Enkel het opzoeken van de methodes in de tabel wordt nu vervangen door het aanspreken van de dll. De code wordt weergegeven in Appendix B. We duiden met using FilterModel aan dat we gebruik zullen maken van de namespace FilterModel. Bij het aanmaken van een dllFilter zal er een nieuw Model worden aangemaakt en zal hier meteen de CreateServiceClassObj methode worden bij opgeroepen. Dit zal dan een ServiceClassObj object teruggeven dat door de filter telkens zal toegevoegd worden aan de parameters. Zoals reeds vermeld, zullen alle filters dezelfde dll (en dus dezelfde klasse) aanspreken, waardoor het nodig is om het modelprogramma toegankelijk te maken voor meerdere clients. Een globale, statische variabele werkt hier niet, zo is er immers maar 1. Bij het oproepen van de methode StartCall zal het ServiceClassObj object, gelijkaardig aan de methode in de xmlFilter, als eerste element van de array van parameters worden toegevoegd. Vervolgens wordt het type van het model opgevraagd, daaraan wordt de methode met gegeven methodenaam opgevraagd en deze wordt dan opgeroepen op het model met de rij van parameters. Indien deze oproep een exceptie gooit, is het mogelijk dat de methode is opgesplitst in het modelprogramma. Daarom wordt de exceptie opgevangen en wordt gekeken of de methode voorafgegaan door Begin wel kan uitgevoerd worden. Indien dit zo is, wordt de boolean in progress op true gezet, zodat bij het oproepen van ReturnCall geweten is of de teruggegeven waarde nog moet onderzocht worden.
3.3
Besluit
We merken dat Spec Explorer zich vrij goed leent tot het specificeren van het verwachte gedrag van een client. Deze modelprogramma’s kunnen gebruikt worden om methode invocaties te verifi¨eren. De modelprogramma’s staan los van de eigenlijke implementatie van de server. Er kunnen dus ook filters gemaakt worden voor bestaande applicaties zonder dat hierbij de code moet opengebroken worden. Een eerste soort filter is de dllFilter, waarbij er gebruik wordt gemaakt van het tot dll gecompileerde modelprogramma. Het andere type filter, de xmlFilter, zal werken met het xmlbestand dat gegenereerd is uit de testgrafe van het modelprogramma. Beide filters werken op gelijkaardige manier. Enkel de effectieve oproepen aan het modelprogramma gebeuren anders. 33
We zullen steeds werken met modelprogramma’s voor meerdere clients, zowel voor de dllFilter als de xmlFilter. De modelprogramma’s moeten ook voldoen aan een aantal voorwaarden die zijn omschreven in Subsectie 3.2.1. Hoe de oproepen onderschept worden en de filters worden aangesproken, zal besproken worden in de volgende hoofdstukken. Hoofdstuk 4 zal bespreken hoe de interceptie gebeurt bij .NET Remoting. Hoofdstuk 5 zal dit bespreken voor ASP.NET. Het grote verschil tussen beide is het feit dat .NET Remoting stateful is en ASP.NET stateless.
34
Hoofdstuk 4
Case study 1: .NET Remoting Zoals reeds vermeld in vorig hoofdstuk, bestaan er heel wat technologie¨en om gedistribueerde client-serverapplicaties te bouwen. In dit hoofdstuk zullen we hier 1 mogelijkheid van bespreken: .NET Remoting. Voor de komst van het .NET raamwerk1 , maakte men gedistribueerde applicaties op Microsoft platformen met DCOM. DCOM is gebaseerd op de componentenarchitectuur COM (Common Object Model). Deze beperkte zich tot componenten die zich op dezelfde host bevonden. DCOM (Distributed COM) maak ook methode-oproepen tussen COM-objecten op verschillende hosts mogelijk. Net zoals het .NET raamwerk COM heeft vervangen om componenten te bouwen, zo heeft .NET Remoting DCOM vervangen om gedistribueerde applicaties te bouwen in .NET. Dit hoofdstuk zal bespreken wat .NET Remoting is en hoe hierbij methode-oproepen kunnen onderschept worden. In Sectie 4.1 wordt het .NET Remoting concept besproken. In Sectie 4.2 gaan we dieper in op de remotable types. Hoe remote objecten geactiveerd worden, wordt besproken in Sectie 4.3. In Sectie 4.4 wordt bekeken wat de mogelijke kanalen zijn die voor communicatie tussen remote objecten worden gebruikt. Vervolgens wordt in Sectie 4.5 besproken wat message sinks en channel sinks zijn. In Sectie 4.6 wordt bekeken hoe we gebruik kunnen maken van deze sinks om een application-level stateful firewall te maken. We concluderen dit hoofdstuk met een besluit in Sectie 4.8.
4.1
.NET Remoting
.NET Remoting is de verzamelnaam voor diensten om .NET objecten op verschillende servers met elkaar te laten interageren. .NET Remoting zal remote objecten lokaal laten lijken. .NET Remoting maakt gebruik van het Common Type System (CTS) en de Common Language Runtime (CLR). De CTS definieert de basistypes die alle .NET-talen moeten ondersteunen. .NET Remoting kan volledig steunen op de object-geori¨enteerde eigenschappen van .NET. De CLR is de commerci¨ele Microsoft implementatie van de Common Language Infrastructure 1
.NET is de Microsoft Web services strategie om informatie, mensen, systemen en apparaten te verbinden door software. Ge¨ıntegreerd in het Microsoft platform, biedt de .NET technologie de mogelijkheid om snel te compileren, uit te voeren, te onderhouden en gebruik te maken van de web service oplossingen [15].
35
(CLI). De CLI bepaalt een internationale standaard om ontwikkelings- en uitvoeringsomgevingen te maken waarin talen en libraries foutloos samenwerken. De CLR en CTS maken het mogelijk om eenzelfde object systeem te gebruiken voor zowel lokale als remote objecten.
4.2
Remotable types
Een remotable type kan de .NET Remoting grenzen2 oversteken of is toegankelijk door de grenzen heen. • Marshal-by-Value Marshal-by-value objecten kunnen de .NET Remoting grenzen oversteken met behulp van serialisatie. Serialisatie zorgt voor de encodering van een object in een bitstroom. Deze bitstroom wordt over de grenzen heen gestuurd naar een andere applicatie en zal daar gedeserialiseerd worden tot een exacte kopie van het oorspronkelijke object. • Marshal-by-Reference Bij Marshal-by-Reference objecten zal er niet geserialiseerd worden, maar zal een referentie geserialiseerd worden tot een proxy op de client. Deze proxy zal zich aan de client-zijde gedragen als het remote object. In deze thesis zal gewerkt worden met Marshal-by-reference objecten.
4.3
Object activatie
Vooraleer een object toegankelijk is, moet het gecre¨eerd en ge¨ınitialiseerd worden. Dit proces wordt activatie genoemd. Marshal-by-reference objecten kennen 2 soorten activatie: serveractivatie en client-activatie: • Server-activatie Als de client een door de server geactiveerd object aanmaakt, wordt er nog niet direct een remote object aangemaakt. Er wordt alleen lokaal een proxy gecre¨eerd. Als de eerste methode-aanroep via de proxy op het remote object wordt gedaan, zal de server het object instanti¨eren; vandaar de naam server-activatie, omdat de server het moment van activeren bepaalt. Server-geactiveerde objecten worden ook wel “wellknown”objecten genoemd. Er bestaan twee soorten server-geactiveerde objecten: – Single Call Bij single call-activatie zal per methode-oproep een nieuw remote object worden aangemaakt. Na de oproep wordt dit object verwijderd. Gezien de korte levensduur, kunnen ze geen toestandsinformatie bijhouden en zijn deze objecten dus niet geschikt voor stateful programmeren. – Singleton Bij singleton-activatie wordt er maximum 1 object aangemaakt. Alle clients zullen dus met hetzelfde object verbonden worden. • Client-activatie CAO’s (client activated objecten) zijn objecten aan de serverkant die geactiveerd worden op vraag van de client. Wanneer de client een nieuw remote object vraagt (met de “new”operator), wordt een activatie-request gestuurd naar de remote 2
Een .NET Remoting grens vormt de scheiding tussen verschillende applicatie domeinen.
36
applicatie. De server zal een object aanmaken en een referentie (ObjRef) terugsturen naar de client applicatie. Met behulp van deze referentie wordt een proxy aangemaakt aan de clientzijde. De methode-oproepen van de client zullen worden uitgevoerd op deze proxy. CAO’s kunnen toestandsinformatie bewaren voor hun specifieke client. Aangezien het doel van deze thesis is om een applicatie-level stateful firewall te cre¨eren, is het erg belangrijk om informatie over de toestanden te hebben. Daarom gaan we werken met client-activated objecten. De client zal het proxy object gebruiken om te interageren met het remote object dat voorgesteld is door de ObjRef. .NET Remoting maakt gebruik van 2 soorten proxies om perfecte interactie te bekomen tussen de client en het remote object: • Transparante proxy De client heeft directe toegang tot de transparante proxy. Wanneer van een ObjRef een proxy wordt gemaakt (unmarshalling), wordt er on-the-fly een TransparentProxy instantie aangemaakt. Wanneer de client een methode-oproep doet op de transparante proxy, zal deze de methode-oproep omzetten in een bericht-object. De bericht object wordt dan doorgestuurd naar de real proxy. • Echte proxy (Real proxy)De echte proxy zal het bericht dat gemaakt werd door de transparante proxy verzenden naar de .NET Remoting infrastructuur om het af te leveren aan het remote object. Dit wordt ge¨ıllustreerd in Figuur 4.1.
4.4
Kanalen
.NET Remoting zal de geserialiseerde bericht-objecten over de grenzen transporteren over kanalen (channels). Er werden 2 soorten kanalen voorzien: TCP en HTTP. • TCP Het TCP-kanaal levert transport gebaseerd op sockets, wat zorgt voor maximale effici¨entie. Het TCP-kanaal type zal berichtobjecten standaard serialiseren tot een binaire stream. • HTTP Het HTTP-kanaal zal minder effici¨ent werken, maar zal wel zorgen voor maximale interoperabiliteit. De geserialiseerde berichtstroom kan niet enkel getransporteerd worden over .NET Remoting grenzen, maar ook over het Internet en langs firewall. De berichtobjecten worden hier standaard geserialiseerd tot een SOAP-formaat3 . In deze thesis wordt gebruik gemaakt van HTTP-kanalen en het SOAP-formaat. De kanaalarchitectuur is erg flexibel omdat er gebruik gemaakt wordt van channel sink objecten die samen een sink chain vormen. In het algemeen vormt elke channel sink een aantal taken uit: 1. Accepteren van het bericht en de stream van de vorige sink in de ketting 3 SOAP (Simple Object Access Protocol) is een protocol voor het transporteren van XML-gebaseerde berichten over een netwerk
37
Figuur 4.1: .NET Remoting maakt gebruik van 2 soorten proxies 2. Een bepaalde actie uitvoeren op de stream of het bericht 3. Het (mogelijke gewijzigde) bericht en de (mogelijk gewijzigde) stream doorgeven aan de volgende sink in de ketting. Dit wordt uitgebreid besproken in volgende sectie.
4.5 4.5.1
Message sinks en channel sinks Channel sinks
Kanalen zenden elk bericht over een ketting van channel sink objecten (sink chain). Deze sink chain bevat sinks die gebruikt worden voor basis kanaalfunctionaliteit, zoals de formatter sink en transport sink. De formatter sink zal berichten serialiseren en de transport sink zorgt voor het eigenlijke versturen over het kanaal. Het is echter ook mogelijk om de channel sink chain aan te passen om speciale taken uit te voeren op het bericht of de stream. Elke channel sink implementeert ofwel de IClientChannelSink interface of de IServerChannelSink interface. De eerste channel sink aan de clientzijde moet ook IMessageSink implementeren. Deze wordt de formatter sink genoemd omdat deze het inkomende bericht (een IMessageobject) zal serialiseren tot een stream. Dit is gelijkaardig voor de serverzijde waar de formatter sink de binnenkomende berichten zal deserialiseren tot IMessage objecten. 38
Figuur 4.2: Channel sink chain 39
Figuur 4.2 geeft een grafische weergave van de channel sink chain. • Het client object doet een methode-oproep. • De transparante proxy zal de methode-oproep omzetten in een message object. Dit message object bevat alle informatie over de methode-oproep, zoals de methodenaam, de argumenten, enz. • De real proxy zal dit message object doorgeven aan een set van message sinks. • Het laatste message sink object zal het message object doorgeven aan de formatter sink, waar het message object zal geserialiseerd worden tot een stream (normaal gezien in binair of soap-formaat) en waar de transport headers worden aangemaakt. Serialisatie is nodig omdat enkel geserialiseerde objecten de .NET Remoting grenzen kunnen oversteken. • De formatter sink geeft deze stream (en het message object) door aan de volgende sink. Let wel op dat het message object niet meer kan gewijzigd worden omdat dit reeds geserialiseerd is. Hier kunnen custom channel sinks worden toegevoegd. • Uiteindelijk komt het geserialiseerde object in streamvorm (en het message object) toe bij de transport sink. De transport sink is verantwoordelijk voor het transporteren van de stream en de transport headers over het netwerk door gebruik te maken van het voorop gesteld transport protocol (HTTP of TCP). • De ontvangst van het bericht aan de serverkant gebeurt op analoge wijze. De transport sink van de server ontvangt een geserialiseerd message object in streamvorm en de transport headers. • Deze stream en headers worden doorgegeven aan de daarop volgende channel sinks, zodat deze uiteindelijk terecht komen bij de formatter sink van de server. Deze zal de stream deserialiseren tot een IMessage object. • Dit message object wordt vervolgens doorgegeven aan de StackBuilderSink, die de eigenlijke methode-oproep op het remote object zal doen. Wanneer de methode-oproep terugkeert, zal de StackBuilderSink het teruggegeven resultaat verpakken in een message object. Dit message object wordt dan analoog teruggestuurd. Channel sink providers (objecten die IClientChannelSinkProvider, IClientFormatterSinkProvider of IServerChannelSinkProvider implementeren) zijn verantwoordelijk voor het aanmaken van channel sinks. Wanneer een remote type wordt geactiveerd, wordt de channel sink provider van het kanaal opgeroepen. De CreateSink methode wordt aangeroepen op de sink provider om de eerste channel sink van de sink chain op te vragen. Channel sinks zijn aan elkaar gelinkt en vormen zo de sink chain. Wanneer de CreateSink methode wordt opgeroepen op de sink provider, moeten volgende zaken zeker gebeuren: • Zijn eigen channel sink aanmaken • De CreateSink methode van de volgende sink provider oproepen 40
• ervoor zorgen dat de volgende sink en de huidige sink aan elkaar gelinkt zijn • De sink teruggeven aan de oproeper van de CreateSink methode. Channel sinks zijn dus verantwoordelijk voor het doorsturen van alle oproepen die zij krijgen, naar de volgende sink in de sink chain. Ze moeten dus een referentie opslaan naar de volgende sink. Het is mogelijk om een zelf gedefinieerde channel sink toe te voegen tussen de formatter sink en de transport sink. Hier is het echter enkel mogelijk om het message object te onderzoeken. Wijzigen is niet meer mogelijk aangezien het message object reeds geserialiseerd is tot een stream. Om een nieuwe channel sink aan te maken, moet men het remoting systeem zo implementeren en configureren dat deze een IServerChannelSinkProvider of IClientChannelSinkProvider implementatie zal herkennen. Deze zal dan ervoor zorgen dat de zelfgedefinieerde IClientChannelSink of IServerChannelSink implementatie aangemaakt wordt en gelinkt wordt aan de andere sinks in de sink chain. Wanneer men meerdere channel sink providers gebruikt en dit aangeeft in een configuratiebestand, zal het remoting systeem de providers aan elkaar linken in de volgorde van het configuratie-bestand. De channel sink providers worden aangemaakt wanneer het kanaal wordt gecre¨eerd, tijdens de RemotingConfiguration.Configure oproep. Wanneer men toch het message object wilt wijzigen moet men het object onderscheppen voor de formatter sink aan de clientzijde of na de formatter aan de serverkant. Aangezien men dan zuiver met IMessage objecten werkt, spreekt men van message sinks.
4.5.2
Message sinks
Elk type dat de IMessageSink interface implementeert, kan werken als een message sink. Message sinks behoren tot elke chain en bevinden zich voor de channel sinks aan de clientkant en achter de channel sinks aan de serverkant. Dit wordt ge¨ıllustreerd in Figuur 4.3[16]. Bij message sinks is het concept “context” erg belangrijk. Contexten bestaan binnen applicatiedomeinen. Een context garandeert dat een set van constraints en gebruikssemantiek de toegang tot de objecten binnen die context zal beheren. Elk applicatie-domein bestaat uit minstens 1 context, de default context. Tenzij een object expliciet een gespecialiseerde context eist, zal de CLR het object aanmaken in de default context. Wanneer men gebruik maakt van contexten, vormen niet alleen de applicatie-domeinen de .NET Remoting grenzen, ook tussen verschillende contexten bevindt zich een .NET Remoting grens. Een type dat overerft van System.ContextBoundObject is een context-gebonden object. Dit betekent dat deze een speciale omgeving of context nodig heeft om zijn instanties in uit te voeren. In deze thesis zal echter niet gewerkt worden met expliciet context-gebonden types. Alle objecten worden standaard aangemaakt in hun default context. Voor meer informatie over het gebruik van context-gebonden types, zie [16]. 41
Figuur 4.3: Message sink chain 42
De .NET Remoting infrastructuur isoleert objecten in een bepaalde context van objecten buiten deze context met behulp van een speciaal type kanaal, het cross-context kanaal en 4 message sink chains zoals te zien in Figuur 4.3 [16]. • Client context sink chain onderschept methode-oproepen gemaakt door objecten binnen de context op objecten die zich buiten de context bevinden. Deze chain is de laatste sink chain voordat de objecten hun context verlaten. Deze chain is contextwijd zodat alle uitgaande methode-oproepen gemaakt door gelijk welke objecten binnen de context door deze chain komen. • Server context sink chain onderschept methode-oproepen op gelijk welk object binnen de context gemaakt door objecten die zich buiten de context bevinden. Dit is de tegenhanger van de client context sink chain. Deze chain is de eerste chain waarlangs de berichten binnenkomen in de context. • Server object sink chain onderschept methode-oproepen gemaakt op een specifiek context-gebonden object. Er is geen overeenkomstige sink chain om methode-oproepen gemaakt binnen de context op objecten buiten de context, te onderscheppen. De laatste sink in deze sink chain zal het bericht doorgeven aan de StackBuilderSink, die de methode oproept op het object. • envoy sink chain onderschept methode-oproepen gemaakt door een client van het context-gebonden object. De envoy sink chain wordt uitgevoerd in de context van de client. Er bestaat een envoy sink chain voor elke proxy naar een remote object. De werking van de message sinks is gelijkaardig aan die van de channel sinks. Elke sink moet een referentie hebben naar de volgende sink in de sink chain. Hoe message sinks kunnen toegevoegd worden aan de message sink chain, is uitgebreid beschreven in [16].
4.6
Custom sink voor application-level stateful firewall
We willen de methode-oproepen onderscheppen net voordat ze worden opgeroepen op het remote object. De interceptie dient dus te gebeuren aan de serverkant. We zullen per remote object een sessie aanmaken. Een client kan dus meerdere sessies hebben en dus meerdere filters. De firewall zal onderzoeken of de methode-oproepen mogen uitgevoerd worden. Hiervoor hebben we informatie over de methode-oproepen nodig. Deze informatie is te vinden in de IMessage objecten. IMessage objecten zijn pas toegankelijk nadat de formatter sink de binnenkomende stream heeft omgezet in een IMessage object. We dienen dus een sink toe te voegen na de formatter sink. Aangezien we zullen werken met IMessage objecten, zullen we een message sink cre¨eren. Aangezien we met default contexten werken, is het ook mogelijk om message sinks toe te voegen op dezelfde manier als channel sinks. We maken een sink provider aan, waar we naar verwijzen in het configuratie-bestand. Deze sink provider zal dan het gewenste sink object aanmaken. Voor het cre¨eren van de nodige custom sink, maken we gebruik van de CustomSinks library[20]. 43
Deze library biedt een flexibel raamwerk voor het cre¨eren van custom sinks. De library bevat een basisklasse voor sinks, BaseCustomSink, een basisklasse voor sink providers, BaseCustomSinkProvider, en een ge¨ımplementeerde client sink provider (CustomClientSinkProvider) en server sink provider (CustomServerSinkProvider). Dit raamwerk leent zich dus perfect tot het maken van de custom sink die nodig is voor de firewall. Aangezien we reeds beschikken over de CustomServerSinkProvider, dienen we enkel nog onze custom sink klasse, MethodToFilterSink, te maken. Voor de code, zie Appendix C. Deze klasse moet overerven van de BaseCustomSink klasse. In de MethodToFilterSink klasse moeten we de methode ProcessRequest en ProcessResponse nog overschrijven. We moeten er voor zorgen dat elke methode naar de juiste filter wordt doorgestuurd. Elke filter hoort immers bij 1 bepaald remote object (we cre¨eren dus een sessie per remote object). Elk marshal-by-reference object krijgt bij activatie een URI (Uniform Resource Identifier) toegewezen, die gebruikt wordt om naar dat object te refereren. Elke filter zal dus gekoppeld worden aan een URI. In de methode ProcessRequest gaan we na wat de URI is van het binnengekomen bericht. Indien de methode-oproep een activatie-oproep is, zal de URI telkens gelijk zijn aan “/RemoteActivationService.rem ”zijn. De oproepen zullen we steeds doorlaten, zonder door te geven aan een filter. We gaan er immers van uit dat activatie steeds toegestaan is. Indien de methode-oproep geen activatie-oproep is, zal de URI een unieke string zijn, die steeds hetzelfde is voor een bepaald remote object. Aan de hand hiervan kunnen we dus het bericht doorgeven aan de bijhorende filter. We gaan een Hashtable gebruiken waarin we filters kunnen opzoeken aan de hand van hun URI. Als we een methode-oproep binnen krijgen, gaan we dus nagaan of de URI reeds gebruikt is. Als de URI nog niet is opgenomen in de Hashtable (eerste oproep na activatie) zal er een nieuwe filter worden aangemaakt, die wordt toegevoegd aan de Hashtable samen met de bijhorende URI. We zullen de StartCall methode oproepen op de nieuwe filter. Hieraan zullen we de methodenaam en de parameters meegeven die we uit het IMessage object hebben gehaald. Als de URI reeds gebruikt was en dus terug te vinden is in de Hashtable, zal op de bijhorende filter de StartCall oproepen en hieraan de methodenaam en parameters meegeven. Bij ProcessResponse gebeurt de verwerking gelijkaardig. Als er een activatie-oproep binnenkomt, zal er geen filter worden aangesproken. Indien de methode-oproep geen activatie is, zal de filter horend bij de URI van het bericht worden opgezocht en zal hierop de ReturnCall methode worden opgeroepen. Hier wordt de methodenaam en ook de teruggegeven waarde doorgegeven. In dit geval is het niet mogelijk dat de URI nog niet gekend is, aangezien deze eerst de ProcessRequest methode is doorgegaan. De helpmethode GetFilter bepaalt wat het type is van de filter. Hier kan dus aangepast worden of men werkt met de XMLFilter of de DllFilter die we beschreven hebben in Hoofdstuk 3. Uiteraard moeten we in het configuratie-bestand aangeven dat we gebruik maken van een eigen sink provider en dat deze MethodToFilterSinks zal aanmaken. Het configuratie-bestand zal er dan uitzien als volgt:
44
<system.runtime.remoting> <serverProviders> <provider type="CustomSinks.CustomServerSinkProvider, CustomSinks" customSinkType="MySinks.MethodToFilterSink, MySinks" /> • geeft aan dat de server werkt met een HTTPkanaal en luistert op de poort met poortnummmer 8989. •
4.7
Moeilijkheden
Om de firewall te testen in .NET Remoting, hebben we een aantal eenvoudige applicaties gebouwd, zoals het licentieovereenkomst voorbeeld en het voorbeeld waarbij men toegang wil 45
bekomen tot een bestand of directory. Bij het maken van deze .NET Remoting applicaties en de MethodToFilterSink, zijn we eigenlijk geen problemen tegengekomen. De grootste uitdaging was het werken met ongekende technologie¨en. Dankzij de uitgebreide documentatie die beschikbaar is, bleek dit geen probleem te zijn.
4.8
Besluit
.NET Remoting is de .NET benadering om gedistribueerde applicaties te maken. .NET Remoting laat zowel stateless als stateful communicatie toe. In deze thesis maken we gebruik van stateful communicatie. Om methode-oproepen te onderscheppen in .NET Remoting, maakt men gebruik van custom sinks. Afhankelijk van het doel zullen dit channel sinks of message sinks zijn. Channel sinks zullen werken met een stream, de geserialiseerde versie van het bericht. Message sinks zullen werken met IMessage objecten. Dit zijn containers waarin alle informatie over de methode-oproep opgeslagen zit. We maken gebruik van een library[20] die het maken van custom sinks vergemakkelijkt. We maken een MethodToFilterSink, een sink die zich aan de serverkant bevindt, achter de formatter sink. Deze sink zal van elk binnenkomend bericht de URI matchen met de bijhorende filter. Elke filter hoort immers bij exact 1 remote object en elk marshal-by-reference object heeft een unieke URI. Op deze filter zal de StartCall of ReturnCall methode worden opgeroepen, zodat de filter kan onderzoeken of de methode mag uitgevoerd worden.
46
Hoofdstuk 5
Case study 2: ASP.NET Om de onafhankelijkheid van de application-level stateful firewall te testen, is het belangrijk om meer dan 1 technologie te bekijken die client-server applicaties ontwikkelt. Het grote verschil tussen .NET Remoting en ASP.NET is dat ASP.NET stateless communicatie voorziet, terwijl dit bij .NET Remoting stateful was. Onze firewall is echter stateful. We moeten dus de communicatie van ASP.NET stateful proberen te maken.
5.1
ASP.NET
ASP.NET is een technologie om krachtige, dynamische webapplicatie te bouwen. Het maakt deel uit van het .NET raamwerk. ASP staat voor Active Server Pages en is ontwikkeld door Microsoft. ASP.NET maakt het mogelijk om op een webserver webpagina’s aan te maken met behulp van programmacode. Er kunnen dus dynamische pagina’s gemaakt worden. Dit betekent dat afhankelijk van de acties van de gebruikers, verschillende gegevens zullen getoond worden. Men kan een ASP.NET pagina herkennen aan de .aspx extensie in plaats van de standaard .htm of .html extensie. ASP.NET maakt uitgebreid gebruik van controls. Dit zijn pagina-onderdelen zoals labels, knoppen, keuzelijsten en tekstvakken. Een pagina wordt samengesteld uit controls. Van elke control dienen vervolgens de bijhorende eigenschappen ingesteld te worden. Controls bevatten programmacode en zijn dus verantwoordelijk voor het aanmaken van paginacode in HTML-formaat.
5.2
HTTP modules
Gelijkaardig aan .NET Remoting, is het mogelijk om in ASP.NET webpagina oproepen te pre- en post-processen. Dit kan gebeuren door een filter chain te cre¨eren. Het .NET raamwerk voorziet een IHttpModule interface. Filters die deze interface implementeren, kunnen toegevoegd worden aan een rij van gebeurtenissen (events) die gedefinieerd zijn door het raamwerk.
47
ASP.NET voorziet een reeks events waar de applicatie kan gebruik van maken tijdens het verwerken van de requests. Het algemene proces om een custom HTTP module te maken is: 1. Implementeer de IHttpModule interface 2. Pas de Init methode aan en registreer hier de events die nodig zijn 3. Zorg voor methodes die deze events zullen afhandelen 4. Registreer de module in het web.config bestand
5.2.1
Events
ASP.NET voorziet een heel aantal events. We zullen deze kort toelichten. Een eerste reeks events vindt plaats voordat de oproep verwerkt wordt (lijst in volgorde van gebeurtenis): • BeginRequest Dit event duidt aan dat er een nieuwe oproep is • AuthenticateRequest Hier wordt gemeld dat het authenticatiemechanisme de oproep geauthenticeerd heeft. • AuthorizeRequest Net zoals AuthenticateRequest, geeft dit event aan dat de oproep 1 stap verder in de chain is en dat de oproep geautoriseerd is. • ResolveRequestCache Dit event wordt opgeroepen om te bepalen of de oproep kan uitgevoerd worden door de inhoud van de output cache terug te geven. Dit is afhankelijk van de manier waarop de output caching opgezet is. • AcquireRequestState Hier wordt aangegeven dat de ASP.NET runtime klaar is om de Session state van de huidige oproep te verwerven. • PreRequestHandlerExecute Dit event geeft weer dat de request handler zal worden uitgevoerd. Dit is het laatste event vooraleer de HTTP handler voor deze oproep wordt aangesproken. De volgende reeks vindt plaats nadat de oproep verwerkt is (lijst in volgorde van gebeurtenis): • PostRequestHandlerExecute Dit event duidt aan da de HTTP handler klaar is met het verwerken van de oproep. • ReleaseRequestState Hier wordt aangegeven dat de oproep-toestand dient opgeslagen te worden omdat de applicatie klaar is met het verwerken van de oproep. • UpdateRequestCache Dit event geeft weer dat het verwerken van de code is afgerond en dat het bestand klaar is om toegevoegd te worden aan de ASP.NET cache. • EndRequest Hier wordt aangeduid dat alle verwerking van de oproep klaar is. Dit is het laatste event vooraleer de applicatie eindigt. 48
Er bestaan ook 3 events die in gelijk welke volgorde kunnen gebeuren: • PreSendRequestHeaders Dit event duidt aan dat de HTTP headers zullen doorgestuurd worden naar de client. Hier kan men dus de headers wijzigen of verwijderen • PreSendRequestContent Hier wordt aangegeven dat de inhoud zal worden doorgestuurd naar de client. Het is dus mogelijk om de inhoud te wijzigen voor deze verzonden wordt. • Error Dit event signaleert een exception.
5.3
Custom HTTP module
In tegenstelling tot .NET Remoting, zullen we een sessie cre¨eren per client. Elke client behoort dus tot exact 1 sessie, terwijl in .NET Remoting we een sessie per remote object gedefinieerd en een client dus tot meerdere sessies kon behoren. Om de oproepen te onderscheppen en na te gaan of deze voldoen aan het verwachte gedrag, dienen we te onderscheppen op een plaats waar de sessie-toestand gekend is. Dit is vanaf AcquireRequestState tot voor ReleaseRequestState. AcquireRequestState zegt immers dat de toestandsinformatie verworven is. ReleaseRequestState geeft aan dat deze informatie terug vrijgegeven is. Ten eerste dienen we de oproep te onderscheppen. Dit kan dus gebeuren in AcquireRequestState of PreRequestHandlerExecute. We willen zo dicht mogelijk bij de server onderscheppen, dus we kiezen de laatste van de events: de PreRequestHandlerExecute. Ten tweede dienen we ook de terugkerende oproep te onderscheppen. Hier zullen we ook zo dicht mogelijk bij de server onderscheppen. Er rest ons sowieso slechts 1 mogelijkheid, omdat de toestandsinformatie niet meer gekend is vanaf ReleaseRequestState. We gaan dus de terugkerende oproep onderscheppen bij het event PostRequestHandlerExecute. De code is weergegeven in Appendix D. In de Init methode wordt dus aangegeven dat het PreRequestHandlerExecute event de methode BeginRequest zal oproepen. Het PostRequestHandlerExecute event zal de methode EndRequest oproepen. In de BeginRequest methode zal worden nagegaan wat de ID is van de sessie van de oproep. Dit is gelijkaardig aan de MethodToFilterSink die besproken is in Hoofdstuk 4, waar aan de hand van de URI werd bepaald bij welk remote object de oproep hoorde. Aan de hand van de sessionID zal bepaald worden welke filter er zal aangesproken worden. Gelijkaardig aan de MethodToFilterSink zal bij de eerste oproep een filter worden aangemaakt die met de sessionID als sleutel wordt toegevoegd aan de hashtable. Bij de EndRequest methode gebeurt het opzoeken van de filter gelijkaardig. Aan de hand van de sessionID zal de bijhorende filter in de hashtable worden opgezocht en zal hierop de ReturnCall methode worden opgeroepen. Alle informatie over de oproep kan opgevraagd worden aan het HttpApplication object dat meegegeven wordt aan de BeginRequest en EndRequest methodes. Het doorgeven van parameters gebeurt hier echter gecompliceerder dan bij de .NET Remoting infrastructuur. Dit wordt uitgebreid toegelicht in volgende sectie. 49
5.4
Moeilijkheden
Het maken van een generieke interceptor voor ASP.NET applicaties is niet zo voor de hand liggend. Zo worden de parameters in ongekende volgorde doorgegeven. Ook worden telkens alle variabelen doorgegeven, dus men zal ook variabelen doorsturen die de methode niet als parameter heeft. In de huidige code is dit nog niet opgelost. Hier wordt de InterceptingModule aangepast bij elke wijziging van de applicatie om de parameters in de juiste volgorde door te geven aan de filter. Voorlopig worden ook telkens alle parameters doorgegeven, wat er voor zorgt dat ook de modelprogramma’s moeten voorzien zijn op deze overbodige parameters. Een oplossing hiervoor is het gebruik van een xmlbestand en aan de hand hiervan bepalen welke methodes welke parameters in welke volgorde nodig hebben. Dit zorgt ervoor dat de interceptor toch onafhankelijk van de applicatie kan worden gebruikt. Als xmlbestand zou men gebruik kunnen maken van het bestand gegenereerd door Spec Explorer. Hier staan immers alle methodes met bijhorende parameters beschreven. (Het is in dat geval wel nodig dat exact dezelfde parameternamen gebruikt worden om deze te kunnen gebruiken als sleutel). Een ander belangrijk probleem is het feit dat ASP.NET alle parameters doorstuurt als string. Soms zijn deze parameters echter integers, booleans, of andere types. In de modelprogramma’s wordt er uiteraard gewerkt met deze types. Het is dus belangrijk dat deze geconverteerd worden naar het juiste type, vooraleer de methode-oproep terecht komt bij het modelprogramma. Aangezien de filter niet dient te weten met welke infrastructuur gewerkt wordt, lijkt het best om deze conversie uit te voeren in de interceptor. Ook hier is het gebruik van een xmlbestand aangeraden. Helaas wordt in het door Spec Explorer gegenereerde bestand niets over het type van de parameters verteld. Er zal dus een andere oplossing moeten gevonden worden. Misschien dat dit bij een uitbreiding van Spec Explorer wel mogelijk is. Op dit moment zal hiervoor echter een ander xmlbestand moeten aangemaakt worden. Een laatste moeilijkheid ligt in het feit dat de output teruggegeven wordt als soap-stream. Om hier de teruggegeven waarde uit te halen, dient de stream eerst geparset worden door een xml-lezer om de nodige elementen uit de stream te halen en door te geven aan het modelprogramma. Deze aspecten zijn echter niet uitgewerkt. Het is de bedoeling om te bewijzen dat de application-level stateful firewall niet enkel met de .NET Remoting infrastructuur werkt, maar ook met ASP.NET. Het al dan niet onafhankelijk zijn van de interceptor zal hier geen meerwaarde brengen.
5.5
Besluit
In dit hoofdstuk werd aangetoond dat de applicatie-level stateful firewall niet enkel werkt met .NET Remoting stateful applicatie, maar ook kan gebruikt worden voor ASP.NET applicatie die stateless zijn. Vooraleer de hier boven beschreven interceptor te gebruiken, dienen er nog een aantal optimalisaties uitgevoerd te worden om de interceptor applicatie-onafhankelijk te maken. De aspecten die dienen aangepast te worden zijn het generiek maken van de volgorde
50
en het type van de parameters, evenals het bepalen van de parameters die bij een methode horen. Indien men wil gebruik maken van de ReturnCall methode van de IFilter, zal er ook moeten gezorgd worden dat de outputstream geparset wordt tot betekenisvolle argumenten. Dit wordt uitgebreider beschreven in Sectie 5.4
51
Hoofdstuk 6
Discussie In dit hoofdstuk worden enerzijds de performantie van de filters besproken. Anderzijds worden een aantal bemerkingen gemaakt bij het gebruik van de xmlFilter. Er worden een aantal mogelijke alternatieven voor de keuzes die gemaakt zijn op een rij gezet en er wordt kort ingegaan op de mogelijke uitbreidingen.
6.1
Performantie
Het is belangrijk om te weten wat de performantie is van beide firewalls. Het is immers best mogelijk dat in een omgeving waarin er veel remote objecten worden aangemaakt, de firewall bezwijkt onder de vele oproepen. Aangezien het interceptiemechanisme afhankelijk is van de gebruikte technologie om een client-serverapplicatie te bouwen, zullen we deze factor vast houden. We gaan de test uitvoeren met een .NET Remoting infrastructuur die werkt met HTTPkanalen en SOAPformatters. We gaan de 2 soorten filters onderzoeken op snelheid. We gaan beide filters testen door een client uit te voeren waarin een lus loopt die telkens 2 remote objecten aanmaakt en daarop een aantal oproepen doet. We gaan werken met het databank toegang voorbeeld waarvan het modelprogramma is weergegeven in Subsectie 3.2.2. In Figuur 6.1 wordt voor respectievelijk 250, 500, 750, 1000, 1250 en 1500 runs weergegeven wat de uitvoertijd is in milliseconden. We merken dat de xmlFilter sneller werkt. Dit is logisch aangezien de xmlFilter enkel bij aanmaak een ArrayList moet cre¨eren die rechtstreeks kan aangesproken worden. Bij de dllFilter dient bij elke oproep de dll aangesproken te worden wat voor een kleine vertraging zorgt.
6.2
XmlFilter: sneller dus beter?
In Figuur 6.1 konden we zien dat de xmlFilter beter presteert dan de dllFilter. Betekent dit dan dat de xmlFilter beter is? In Hoofdstuk 3 werden 2 grote nadelen beschreven die ervoor zorgen dat de xmlFilter in vele gevallen verre van optimaal is of zelfs onbruikbaar is. Het eerste nadeel is het feit dat er enkel parameters van een primitief type kunnen meegegeven worden. Het is echter vaak zo dat men wel wil gebruik maken van complexere datatypes. 52
Figuur 6.1: Performantie van de dllFilter en xmlFilter Als voorbeeld een kaartspel. Het zou erg handig zijn moest er tussen de client en de server kunnen gecommuniceerd worden met Cardobjecten, waarin een variabele het soort kaart beschrijft (klaveren, schoppen, ruiten, harten) en een variabele die de waarde weergeeft. De xmlFilter laat dit echter niet toe, zodat er een alternatieve oplossing moet gezocht worden. Men zou in dit geval elke kaart gelijk kunnen stellen aan een getal zodat men met integers kan communiceren of de kaart beschrijven in een string. Natuurlijk moet dit ook mogelijk zijn. Indien we werken met een bestaande applicatie die gebruik maakt van niet-primitieve types, zal dit problematisch worden. Het kaartspel brengt ons bij het volgende nadeel: het beperkte aantal mogelijke parameters. Aangezien het xmlbestand een beschrijving is van een gegenereerde testgrafe, zal er in de meeste gevallen scenario controle nodig zijn om de grafe te kunnen weergeven. Zelfs als men de instellingen van de grafe aanpast en een grafe met een groot aantal transities en toestanden toelaat, zal dit in veel gevallen nog steeds onvoldoende zijn. Zelfs als de grafe wel kan weergegeven worden, moet er rekening gehouden worden met het feit dat het xmlbestand enorm groot zal worden en dat elke filter dus een zeer grote ArrayList zal moeten bijhouden. In vele gevallen zal dus de dllFilter verkozen worden boven de xmlFilter.
6.3
Assembly meerdere keren laden
In deze thesis wordt gewerkt met modelprogramma’s voor meerdere clients. De reden hiervoor is dat we telkens dezelfde dll aanroepen en dat 1 globale, statische variabele dus niet voldoende is. In principe is het eigenlijk wel mogelijk om een assembly meerdere keren te laden. Dit door volgende code toe te passen: byte[] bits = File.ReadAllBytes("mydll.dll"); 53
Figuur 6.2: Performantie van de dllFilter2 waarbij de assembly meerdere keren geladen wordt, de dllFilter en de xmlFilter
Assembly a1 = Assembly.Load(bits); Het probleem is echter dat er per remote object, een aparte filter wordt aangemaakt en dat elke filter opnieuw de dll moet laden. Dit zorgt voor enorm veel overhead. Deze waarden worden getoond in Figuur 6.2. Met dllFilter2 bedoelen we dus de dllFilter die meerdere assemblies laadt, zodat we gebruik kunnen maken van modelprogramma’s voor 1 client. Als we de waardes van dllFilter2 vergelijken met de waardes van de andere filters, merken we dat het laden van meerdere assemblies de uitvoertijd aanzienlijk vergroot. Hoe meer remote objecten men aanmaakt, hoe groter het verschil wordt met de andere filters. Het verschil is zelfs zo groot dat dllFilter2 en de andere 2 filters tot een andere ordegrootte behoren. De dllFilter en xmlFilter behoren tot O(n) terwijl dllFilter2 behoort tot O(n2 ). Dit is duidelijk te zien in Figuur 6.2. Het spreekt dus voor zich dat we van deze filter geen gebruik zullen maken.
6.4
Alternatief voor specificatie: Spec#
Het doel van deze thesis is enerzijds het verwachte gedrag van een client te specificeren en anderzijds de oproepen van de client te onderscheppen en na te gaan of deze overeenkomen met de verwachtingen. In deze thesis hebben we 2 specificatietalen onderzocht. Allereerst hebben we Spec# onderzocht. Deze taal is reeds aangehaald in Hoofdstuk 2. We hebben hier geen gebruik van gemaakt, aangezien deze ervoor zorgt dat de specificatie verweven zit in de eigenlijke implementatie. We willen echter onze firewall onafhankelijk van de clientserverimplementatie houden. Daarom hebben we gewerkt met modelprogramma’s in Spec Explorer.
54
6.5
Alternatieven voor interceptie
In deze sectie worden een aantal mogelijke alternatieve interceptiemechanismen aangehaald, die overwogen zijn tijdens het cre¨eren van de firewall.
6.5.1
custom proxies
Dit zou mogelijk geweest zijn indien we oproepen dienden te onderscheppen aan de clientkant. Aangezien proxies geen aanpasbare laag aan de serverzijde hebben, zullen we hier geen gebruik van maken.
6.5.2
SOAP filters
SOAP filters onderscheppen soap-berichten. Dit is uiteraard een mogelijkheid. Wij gaan echter in plaats van de geserialiseerde berichten, de berichten zelf onderscheppen. Dit houdt ons iets dichter bij de eigenlijke oproepen. Ook is het met custom sinks mogelijk om langs een TCP-kanaal met een binair formaat te communiceren.
6.5.3
Aspecten
Een ander mogelijk alternatief voor custom sinks, is werken met een aspect-geori¨enteerde programmeertaal zoals Aspect#. Hier is het immers mogelijk om pointcuts te defini¨eren. Als een methode matcht met deze pointcut, zal de methode onderschept worden en zal de code die door het aspect gedefinieerd is, worden uitgevoerd vooraleer de eigenlijke methode wordt uitgevoerd. We hebben er echter voor gekozen om een interceptiemechanisme, horend bij de gekozen technologie, te gebruiken.
6.5.4
In servercode
In plaats van de oproepen te onderscheppen net voor ze de server bereiken, is het ook mogelijk om in de code van de server zelf te onderzoeken of de methode mag uitgevoerd worden. Net voordat de methode wordt uitgevoerd, zal de server zelf de filter (of zelfs rechtstreeks het modelprogramma) aanspreken en nagaan of de methode mag uitgevoerd worden. Deze werkwijze willen we uiteraard verwijden, aangezien we een onafhankelijke firewall willen cre¨eren. Het is dus niet de bedoeling om de servercode aan te passen.
6.6
Alternatief technologie: J2EE
Een andere mogelijke remoting infrastructuur is J2EE (Java 2 Enterprise Edition). Het platform biedt een multitier gedistribueerd model, herbruikbare componenten, een beveiligingsmodel, flexibel transactiebeheer en ondersteuning voor webservices door middel van een ge¨ıntegreerde data-uitwisseling gebaseerd op XML. Er zou kunnen gewerkt worden met struts. Deze thesis zal zich echter aan de Microsoft kant houden.
55
6.7
Alternatief firewall: ASP.NET validation
Het is mogelijk om met ASP.NET na te gaan of de ingegeven waardes geldig zijn. Er bestaan verschillende controls die verschillende aspecten kunnen controleren, zoals het vergelijken van ingegeven waarde met een andere waarde, nagaan of een verplicht veld is ingevuld,. . . Het is zelfs mogelijk om eigen controles toe te voegen. Deze controles kunnen ook aan de serverzijde uitgevoerd worden, zodat het omzeilen van deze controles al erg moeilijk wordt. Het nadeel van deze controles, is dat ze stateless gebeuren. Wij willen echter een stateful controle invoeren.
6.8
Uitbreidingen
In deze sectie worden een aantal mogelijke uitbreidingen voor de application-level stateful firewall besproken. Deze uitbreidingen zijn door tijdgebrek niet gerealiseerd.
6.8.1
XmlFilter performanter
In de huidige versie van de xmlFilter wordt nagegaan of een oproep mag uitgevoerd worden door het doorzoeken van een ArrayList. In deze ArrayList bevinden zich immers de gegevens die ingelezen zijn uit het xmlbestand. Om na te gaan of een oproep voldoet aan de verwachtingen wordt er simpelweg ge¨ıtereerd over de elementen van de ArrayList. Dit is uiteraard niet optimaal. Indien hiervoor een ander opslagsysteem gebruikt wordt, kan het opzoekwerk sneller gebeuren en zal de performantie van de xmlFilter nog stijgen.
6.8.2
Niet-primitieve types als parameter
Het gebruik van de xmlFilter zou erg vergemakkelijkt worden indien ook niet-primitieve types kunnen gebruikt worden als parameter. Misschien is het wel mogelijk om een plug-in te maken voor Spec Explorer zodat de niet-primitieve parameters op een duidelijkere manier worden weergegeven (in plaats van het weinig zeggende c0). Misschien kan er wel een soort ToString() methode worden toegevoegd die opgeroepen wordt op de objecten die meegegeven worden als parameter. Uiteraard zal de xmlFilter nog moeten aangepast worden om het mogelijk te maken om een ToString-versie van een object te matchen met een echt object.
56
Hoofdstuk 7
Besluit In Sectie 7.1 zullen we het geleverde werk bespreken aan de hand van de opgestelde doelstellingen. Vervolgens worden in Sectie 7.2 de problemen aangehaald die we zijn tegengekomen tijdens het ontwikkelingsproces. In Sectie 7.3 gaan we het bekomen resultaat vanuit een kritisch standpunt bespreken. Mogelijke uitbreidingen en verder onderzoek wordt beschreven in Sectie 7.4.
7.1
Resultaten
De doelstelling van deze thesis is tweeledig. Enerzijds diende het verwachte gedrag van clients gespecificeerd te worden. Anderzijds moest er een application-level stateful firewall ontwikkeld worden. Deze firewall is verantwoordelijk voor het onderscheppen van oproepen van de client en het controleren van deze oproepen. In deze sectie wordt besproken hoe dit gerealiseerd is.
7.1.1
Beschrijven van verwachte gedrag van clients
Het eerste doel van de thesis is het beschrijven van het verwachte gedrag van de clients. We hebben ervoor gekozen om dit te beschrijven in Spec Explorer. Spec Explorer wordt typisch gebruikt om modelgebaseerd te testen. Het is echter ook mogelijk om het verwachte gedrag van de client te specificeren in modelprogramma’s.
7.1.2
Application-level stateful firewall
Specificatie van het verwachte gedrag is uiteraard onvoldoende. Er dient immers ook nagegaan te worden of de client voldoet aan dit gedrag. Hiervoor hebben we een application-level stateful firewall gecre¨eerd. Het is belangrijk dat deze onafhankelijk van de applicatie kan gebruikt worden. Men moet deze firewall immers ook kunnen gebruiken bij applicaties die reeds gebouwd zijn. De firewall bestaat uit 2 delen: de filter en de interceptor. Er bestaan heel wat technologie¨en waarmee men client-serverapplicaties kan bouwen. Elke technologie beschikt over specifieke onderscheppingsmechanismen. We dienden er dus voor te zorgen dat de onderscheppingscomponent onafhankelijk is van de beslissingscomponent (filter). Dit hebben we gerealiseerd door een interface IFilter aan te bieden aan de onderscheppingscomponent.
57
We hebben 2 verschillende filters gemaakt. Een eerste maakt gebruik van de dll die geleverd wordt door Spec Explorer. Wanneer de filter deze dll aanspreekt, zal er on-the-fly getest worden of de oproep voldoet aan de verwachtingen die gemodelleerd zijn in het modelprogramma van de dll. De tweede filter werkt met een xmlbestand. Dit bestand is een weergave van een gegenereerde testgrafe van een Spec Explorer modelprogramma. De filter slaat alle beschreven toestanden op en gaat na of binnenkomende oproepen kunnen uitgevoerd worden gezien de toestand waarin de filter zich op dat moment bevindt. We hebben 2 verschillende technologie¨en gebruikt om client-serverapplicaties te bouwen. Als eerste hebben we .NET Remoting gebruikt. Om de oproepen te onderscheppen hebben we een custom sink gemaakt, die alle oproepen doorgeeft aan de juiste filter. Om te bepalen welke oproep bij welke filter hoort, hebben we gebruik gemaakt van URI’s. Elk remote object heeft immers een unieke URI. We hebben dus per URI een filter aangemaakt die de bijhorende oproepen zal controleren. Voor het bouwen van de custom sink, hebben we gebruik gemaakt van een library die gebouwd is om eenvoudig custom sinks te cre¨eren. Meer informatie over deze library is te vinden in [20]. Om aan te tonen dat de firewall ook kan gebruikt worden in stateless omgevingen, hebben we de firewall getest op applicaties gemaakt in ASP.NET. Hier konden we nog steeds dezelfde filters gebruiken als bij de applicaties in .NET Remoting (dankzij de IFilter interface). Het onderscheppen diende echter anders te gebeuren. We maken gebruik van een HTTP-module die de oproepen onderschept en doorgeeft aan de filter. Hier zijn we echter op een aantal problemen gestoten die nog niet volledig verholpen zijn. Deze worden toegelicht in Sectie 7.2.
7.2
Moeilijkheden
Tijdens het ontwikkelen van de firewall, zijn we op een aantal problemen gestoten die we in deze sectie zullen bespreken.
7.2.1
Gebreken bij xmlFilter
Zoals reeds uitvoerig aangehaald, zijn er een aantal tekortkomingen bij de xmlFilter. Het eerste minpunt is het feit dat het aantal parameterwaardes beperkt moet blijven. Aangezien het xmlbestand de gegenereerde testgrafe weergeeft, dient het aantal mogelijke toestanden beperkt te zijn. Het grote probleem hierbij is dat de filter niet enkel zal controleren op methodenaam om te verifi¨eren dat de methode mag uitgevoerd worden in de toestand waarin de filter zich bevindt, er wordt ook nagegaan of de parameters horende bij de methode geldig zijn. Men moet dus bij het genereren van de grafe al perfect weten welke waardes de parameters kunnen hebben. Zelfs als de mogelijke waardes gekend zijn, is het mogelijk dat dit teveel toestanden oplevert. Als voorbeeld bekijken we een kaartspel. Bij elke methode die een kaart als parameter meegeeft, zullen alle 52 mogelijkheden in het xmlbestand moeten beschreven zijn. Dit zal al snel oplopen tot een zeer groot aantal toestanden dat niet meer kan beschreven worden door de grafe. Een mogelijke oplossing is om bij de exploratie eigenschappen het aantal toegelaten toestanden en transities te verhogen. Dit zal echter leiden tot een enorm grote grafe, met als gevolg een enorm xmlbestand. Dit zal de performantie zeker niet ten goede komen.
58
Een tweede probleem is het feit dat enkel primitieve types kunnen gebruikt worden als parameter. Een xmlbestand bestaat immers uit tekst en kan dus geen referenties bevatten. Het is echter best mogelijk dat bestaande applicaties wel gebruik maken van complexere types en dus van referenties. Hier kan echter de xmlFilter niet gebruikt worden.
7.2.2
Parameters in ASP.NET
Werken met ASP.NET, bracht heel wat moeilijkheden met zich mee. Wegens tijdgebrek zijn niet alle problemen verholpen. Voor mogelijke oplossingen wordt verwezen naar Sectie 7.4. In tegenstelling tot .NET Remoting, worden in ASP.NET alle parameters doorgegeven in stringformaat. In de filter wordt echter gewerkt met de echte types. Het is dus nodig om deze strings te casten naar hun eigenlijke type. Hoe dit kan worden uitgewerkt, wordt besproken in Sectie 7.4. Ook worden niet enkel de nodige waardes meegegeven, alle variabelen worden bij elke oproep doorgestuurd. De volgorde waarin de variabelen worden doorgegeven, is afhankelijk van applicatie tot applicatie. In Sectie 7.4 wordt uitgelegd hoe deze problemen kunnen opgelost worden.
7.2.3
Onderscheppen van output bij ASP.NET
Onze firewall zorgt ervoor dat ook de teruggegeven waardes kunnen onderschept worden. Dit is vooral van belang om de toestand van het modelprogramma te kunnen updaten. Deze output onderscheppen in ASP.NET is echter niet zo evident. De teruggegeven waarde is geserialiseerd tot outputstream in SOAP-formaat. Om de teruggegeven waarde te onderzoeken, is het nodig om de nodige elementen uit deze stream te halen. Dit is echter voorlopig niet uitgewerkt. Een mogelijke oplossing wordt besproken in Sectie 7.4.
7.3 7.3.1
Kritische bedenkingen Modelprogramma’s voor 1 client
In Hoofdstuk 6 werd reeds aangehaald dat het mogelijk is om een dll meerdere keren te laden. Dit betekent dat het toch mogelijk zou zijn om gebruik te maken van modelprogramma’s voor 1 client en we dus slechts 1 globale toestandsvariabele nodig hebben. We hebben echter gemerkt dat als we in elke filter opnieuw de dll gaan laden, dit de performantie niet ten goede komt. We hebben er dus voor gekozen om de modelprogramma’s iets gecompliceerder te maken zodat deze kunnen aangesproken worden door meerdere clients.
7.3.2
Firewall voor ASP.NET
De application-level stateful firewall is volledig uitgewerkt voor .NET Remoting. Om aan te tonen dat de firewall ook kan gebruikt worden in stateless omgevingen, hebben we deze firewall ook getest voor ASP.NET applicaties. Hierbij hebben we de interceptor aangepast, zodat we geen gebruik maken van custom sinks, maar werken met HTTP modules. In grote lijnen hebben we aangetoond dat de firewall ook werkt voor ASP.NET applicaties. De tijd was echter beperkt zodat niet alle aspecten uitgebreid zijn uitgewerkt voor de ASP.NET 59
applicaties. De aspecten die dienen uitgebreid te worden om optimaal gebruik te kunnen maken, zijn aangehaald in Sectie 7.2 en de mogelijke oplossingen worden nog kort beschreven in Sectie 7.4. De interceptor is echter voldoende uitgewerkt om aan te tonen dat de firewall ook kan werken in een stateless omgeving.
7.3.3
xmlFilter vs. dllFilter
Men kan niet zomaar zeggen dat de ene filter beter is dan de andere. Indien performantie belangrijk is, zal er gekozen worden voor de xmlFilter. Indien men wil werken met referenties als parameter, of men veel mogelijke waardes per parameter heeft, zal men de dllFilter verkiezen. Voorlopig is het immers nog niet mogelijk om met referenties of (oneindig) veel waardes de xmlFilter te gebruiken.
7.4 7.4.1
Verder onderzoek Outputstream in ASP.NET
Om de nodige informatie uit de outputstream te halen, zal men een xml-lezer moeten maken, die deze outputstream zal inlezen en de belangrijke gegevens uit de stream zal halen. We gaan ervan uit dat dit zeker doenbaar is. Helaas is dit niet kunnen geverifieerd worden.
7.4.2
Parameters in ASP.NET
Het eerste probleem bij ASP.NET is het feit dat men niet weet welke variabelen bij welke methodes horen en in welke volgorde deze parameters moeten doorgegeven worden. Een mogelijke oplossing hiervoor zou zijn om het xmlbestand gegenereerd door Spec Explorer in te lezen en aan de hand hiervan te bepalen welke methodes welke parameters nodig hebben en in welke volgorde. Men kan immers de parameternamen gebruiken als sleutel om de waardes op te vragen. Een ander probleem dat dient verholpen te worden, is het casten van de strings naar het juiste type. Hiervoor zou men ook een xmlbestand, waarin staat tot welk type de parameters behoren, kunnen gebruiken. Jammer genoeg wordt dit niet vermeld in het xmlbestand van Spec Explorer en zal hier een andere oplossing voor gezocht moeten worden. Misschien kan het generatieproces van het xmlbestand lichtjes gewijzigd worden, zodat ook het type van de parameters vermeld wordt.
7.4.3
xmlFilter performanter
Voorlopig worden alle gegevens over de toestanden bewaard in een eenvoudige ArrayList. Telkens we willen nagaan of een methode mag uitgevoerd worden, zal de hele lijst overlopen worden totdat er een match gevonden is. Deze match is gebaseerd op de methodenaam en parameters. Dit is uiteraard niet het meest effici¨ente systeem. In verder onderzoek zou kunnen getracht worden om het controleren van de methode performanter te maken.
60
7.4.4
xmlFilter met referenties
In Sectie 7.2 werd reeds vermeld dat de xmlFilter niet overweg kan met referenties als parameter. Een mooie uitbreiding zou zijn om dit toch mogelijk te maken. Hier zal waarschijnlijk een wijziging van het Spec Explorer generatieproces voor nodig zijn. Ook zal de xmlFilter moeten aangepast worden.
7.4.5
xmlFilter met oneindig veel parameterwaardes
Voor sommige applicaties zou het nuttig zijn om gelijk welke waarde van parameter toe te laten (indien dit geen invloed heeft op de toestand). Dit is echter voorlopig niet realiseerbaar. Een mogelijke uitbreiding zou eruit bestaan om dit wel mogelijk te maken. Men kan bijvoorbeeld in plaats van alle mogelijkheden op te sommen in het xmlbestand, bij deze parameter een soort wildcard teken plaatsen dat aangeeft dat de parameterwaarde in dit geval niet belangrijk is. Dit zou betekenen dat het genereren van het Spec Explorer xmlbestand moet gewijzigd worden.
7.5
Conclusie
Deze thesis heeft aangetoond dat het mogelijk is om met behulp van modelprogramma’s in Spec Explorer het verwachte gedrag van clients in een client-serverapplicatie te specificeren. Deze beschrijving kan gebruikt worden door de application-level stateful firewall om na te gaan of oproepen van de client mogen uitgevoerd worden. De firewall bestaat uit 2 componenten. Een eerste component zal zorgen voor de interceptie van de oproepen. Deze component is afhankelijk van de technologie die gebruikt wordt om de client-serverapplicatie te cre¨eren. De andere component zal controleren of de onderschepte oproepen mogen uitgevoerd worden. Dit aan de hand van het modelprogramma dat het verwachte gedrag beschrijft. We hebben ge¨experimenteerd met 2 verschillende filters. Een eerste filter spreekt het tot dll gecompileerde modelprogramma rechtstreeks aan. Er wordt hier on-the-fly gecheckt of de methode mag uitgevoerd worden. De andere filter maakt gebruik van een xmlbestand dat een beschrijving is van de gegenereerde testgrafe van het modelprogramma. De filter zal alle informatie van het xmlbestand inlezen en bijhouden in een tabel. Wanneer een methode wordt gecontroleerd, zal gekeken worden of deze methode met gegeven methodenaam en parameters in de huidige toestand mag uitgevoerd worden. De xmlFilter is de snelste van de 2, maar heeft een aantal nadelen. Zo kunnen er enkel parameters van het primitieve type gebruikt worden en moet men op voorhand weten welke waardes deze parameters kunnen hebben. We hebben aangetoond dat de application-level stateful firewall zowel in een stateful als in een stateless omgeving kan gebruikt worden.
61
Bijlage A
XmlFilter namespace Filters { using FilterModel; using StateTransition; public class XMLFilter: IFilter { private const String filename = "filterModel.xml"; private ArrayList transitionTable= new ArrayList(); private String currentState="S0"; private bool in_progress=false; private String serviceClassObj; public XMLFilter() { transitionTable=StateTransitionTable.GetStateTransitionTable(); Initialize(); } public void StartCall(string methodName, Object[] parameters) { Object[] parameterWithSCObj; if(parameters!=null) { parameterWithSCObj=new Object[parameters.Length+1]; parameterWithSCObj[0]=serviceClassObj; for(int i=0; i<parameters.Length;i++) { parameterWithSCObj[i+1]=parameters[i]; } } else {
62
parameterWithSCObj=new Object[1]; parameterWithSCObj[0]=serviceClassObj; } StateTransition st= GetTransition(methodName, parameterWithSCObj); if(st!=null) { //methodName is found in XML && parameters match currentState=st.GetTarget(); } else { StateTransition start=GetTransition("Begin"+methodName, parameterWithSCObj); if(start !=null) { //"begin"+MethodName is found in XML && parameters match //=> split method in 2 in_progress=true; currentState=start.GetTarget(); } else { //combination methodName + parameters does not exist throw new ArgumentException("this is an illegal call"); } } } public void ReturnCall(string methodName, Object returnValue) { if(in_progress==true) { //method is split Object[] objArray={serviceClassObj, returnObj}; StateTransition end=GetTransition("End"+methodName, objArray); currentState=end.GetTarget(); in_progress=false; } }
private StateTransition GetTransition(String methodName, Object[] parameter) { for(int i=0; i
if(obj.GetSource().Equals(currentState)&& obj.getInvocation().Equals("FilterModel."+methodName)) { if((parameter==null && obj.GetParameters()==null) ||(parameter.Length==0 && obj.GetParameters().Length==0)) { return obj; } else { if(HaveSameParameters(parameter, obj.GetParameters())) return obj; } } } return null; }
private bool HaveSameParameters(Object[] par1, String[] par2) { for(int i=0; i<par2.Length; i++) { if(par1[i]==null) return false; else { if(!(par1[i].ToString().ToUpper()).Equals( par2[i].ToString().ToUpper())) return false; } } return true; } private void Initialize() { StateTransition st= GetTransition("CreateServiceClassObj",new Object[0]); if(st!=null) { serviceClassObj=st.getResult(); currentState=st.GetTarget(); } else { throw new ArgumentException("Impossible to initialize XMLFilter"); } 64
} } }
65
Bijlage B
DllFilter namespace Filters { using FilterModel; public class SpecExplorerFilter: IFilter { private Model model; ServiceClassObj sco; private bool in_progress=false; public SpecExplorerFilter() { model= new Model(); sco=Model.CreateServiceClassObj(); } public void StartCall(string methodName, Object[] parameters) { try { Object[] paramsWithSco=new Object[parameters.Length+1]; if(parameters.Length>0) { paramsWithSco[0] = sco; for(int i=0; i<parameters.Length; i++) { paramsWithSco[i+1]=parameters[i]; } } else { paramsWithSco[0]=sco;
66
} //try to invoke the method in the dll Type modelType=model.GetType(); modelType.GetMethod(methodName).Invoke(model, paramsWithSco); } catch(Exception e) { Object[] paramsWithSco=new Object[parameters.Length+1]; //methodName and/or parameters did not match //try to match "begin"+ methodName and parameters //=> split method if(parameters.Length>0) { paramsWithSco[0] = sco; for(int i=0; i<parameters.Length; i++) { paramsWithSco[i+1]=parameters[i]; } } else { paramsWithSco[0]=sco; } modelType.GetMethod("Begin"+methodName).Invoke(model, paramsWithSco); in_progress=true; } } public void ReturnCall(string methodName, Object returnValue) { if(in_progress==true) { //method is split Type modelType=model.GetType(); Object[]objArray={sco,returnValue}; modelType.GetMethod("End"+methodName).Invoke(model, objArray); in_progress=false; } } } }
67
Bijlage C
MethodToFilterSink using using using using using using using
System; System.IO; System.Runtime.Remoting; System.Runtime.Remoting.Messaging ; System.Runtime.Remoting.Channels; System.Collections; CustomSinks;
namespace MySinks { using CustomSinks; using Filters; public class MethodToFilterSink : BaseCustomSink { private Hashtable filterTable= new Hashtable();
protected override void ProcessRequest(IMessage message, ITransportHeaders headers, ref Stream stream, ref object state) { if(message.Properties["__Uri"].Equals("/RemoteActivationService.rem")) { //creates ServiceClass, //is permitted so do nothing } else { IFilter filter=GetFilter(message.Properties["__Uri"]); filter.StartCall((string)message.Properties["__MethodName"], (Object[])message.Properties["__Args"]); } }
68
protected override void ProcessResponse(IMessage message, ITransportHeaders headers, ref Stream stream, object state) { if(message.Properties["__Uri"].Equals("/RemoteActivationService.rem")) { // do nothing } else{ IFilter filter=GetFilter(message.Properties["__Uri"]); filter.ReturnCall((string)message.Properties["__MethodName"], message.Properties["__Return"]); } }
private IFilter GetFilter(Object uri) { if (filterTable.ContainsKey(uri)) { return (IFilter)filterTable[uri]; } else { //hier type veranderen IFilter newFilter=new DllFilter(); //IFilter newFilter=new XMLFilter(); filterTable.Add(uri,newFilter); return newFilter; } } } }
69
Bijlage D
InterceptingModule using using using using using
System; System.Web; System.Web.SessionState; System.Collections; System.Collections.Specialized;
namespace Filters { public class InterceptingModule : IHttpModule, IReadOnlySessionState { private Hashtable filters=new Hashtable(); //if true, then use xmlFilter, else dllFilter private bool xml=true; //if true, then forward calls to filters private bool filtering=true;
public void Init(HttpApplication httpApp) { // Register our event handler with Application object. httpApp.PreRequestHandlerExecute += new EventHandler(this.BeginRequest) ; httpApp.PostRequestHandlerExecute += new EventHandler(this.EndRequest) ; } public void Dispose() { } private void BeginRequest(object obj, EventArgs evArgs) { if(filtering)
70
{ HttpSessionState sesState=((HttpApplication)obj).Session; if(sesState!=null) { String sesID=sesState.SessionID; if(sesID!=null) { IFilter fil=getFilter(sesID); if(((HttpApplication)obj).Request.Form.AllKeys.Length!=0) { try { //TODO: create generic lookup system NameValueCollection form= ((HttpApplication)obj).Request.Form; Object[]parameters=new Object[form.Keys.Count-2]; String methodname=""; for(int i=1; i ok 71
} } } } } private void EndRequest(object obj, EventArgs evArgs) { if(filtering) { //HttpSessionState sesState=((HttpApplication)obj).Session; HttpSessionState sesState=((HttpApplication)obj).Session; if(sesState!=null) { String sesID=sesState.SessionID; if(sesID!=null) { IFilter fil=getFilter(sesID); try { //TODO: parse outputStream to methodName, //parameters, return value,... + use //generic lookup system //=> currently not used if(((HttpApplication)obj).Request.Form.AllKeys.Length!=0) fil.ReturnCall(((HttpApplication)obj).Request.Form. AllKeys[3], null); } catch(Exception e) { RemoveFilter(sesID); throw new ArgumentException("This is an illegal call"); } } } } } private IFilter getFilter(String sesID) { if(sesID!=null) { Console.WriteLine(sesID); if(!filters.ContainsKey(sesID)) { 72
IFilter filter; if(xml) { filter=new XMLFilter(); } else { filter=new DllFilter(); } filters.Add(sesID, filter); return filter; } else { return (IFilter)filters[sesID]; } } else { return null; } } private void RemoveFilter(String sesID) { filters.Remove(sesID); } } }
73
Bibliografie [1] George Coulouris, Jean Dollimore & Tim Kindberg: Distributed systems: concepts and design. Addison-Wesley, 2001, ISBN 0201-61918-0 [2] Andrew S. Tanenbaum: Computernetwerken, Prentice Hall, 2003, ISBN 90-430-0698-X [3] Colin Campbell, Wolfgang Grieskamp, Lev Nachmanson, Wolfram Schulte, Nikolai Tillmann & Margus Veanes: Model-Based Testing of Object-Oriented Reactive Systems with Spec Explorer. Technical Report, 2005, MSR-TR-2005-59 [4] Colin Campbell, Wolfgang Grieskamp, Lev Nachmanson, Wolfram Schulte, Nikolai Tillmann & Margus Veanes: Model-Based Testing of Object-Oriented Reactive Systems with Spec Explorer. Technical Report, May 2005. [5] Margus Veanes, Colin Campbell, Wolfram Schulte & Pushmeet Kohli: On-The-Fly Testing of Reactive Systems. Microsoft Research Technical Report MSR-TR-2005-05, January 2005. [6] Mike Barnett, K. Rustan M. Leino & Wolfram Schulte: The Spec# programming system: An overview. In CASSIS 2004, LNCS vol. 3362, Springer, 2004. [7] Mike Barnett, Rob DeLine, Manuel F¨ahndrich, K. Rustan M. Leino & Wolfram Schulte: Verification of object-oriented programs with invariants. JOT 3(6), 2004. [8] Spec# , http://research.microsoft.com/specsharp/ [9] Thuan Thai & Hoang Q. Lam: .NET Framework Essentials.O’Reilly, 2002, ISBN 0-59600302-1 [10] Jesse Liberty: Programming C#. O’Reilly, 2002, ISBN 0-596-00309-9 [11] Gregory S. Macbeth: C# programmer’s handbook. Apress, 2004, ISBN 1-59059-270-0 [12] Egon B¨orger & Robert St¨ark: Abstract State Machines: A Method for High-Level System Design and Analysis. (secties 2.2-2.4), Springer-Verlag, 2003 [13] Andrew Stern: Web Application Vulnerabilities, http://www.f5.com/communication/articles/2005/article030905.html [14] Osi-referentiemodel (afbeelding): http://www.3mfuture.com/images/osi-model-7-layers.png [15] .NET framework : http://www.microsoft.com/net/basics.mspx 74
[16] Scott McLean, James Naftel & Kim Williams: Microsoft .NET Remoting, Microsoft Press, 2003, ISBN 0-7356-1778-3 [17] An Introduction to Microsoft .NET Remoting Framework : http://msdn.microsoft.com/library/default.asp?url=/library/enus/dndotnet/html/introremoting.asp [18] .NET Remoting met Visual Basic .NET (deel 1): http://www.microsoft.com/netherlands/msdn/headline/aug01 remoting.aspx [19] .NET Framework Developer’s Guide Sinks and Sink Chains: http://msdn.microsoft.com/library/default.asp?url=/library/enus/cpguide/html/cpconSinksSinkChains.asp [20] Motti Shaked: .NET Remoting Customization Made Easy: Custom Sinks (custom sinks library): http://www.codeproject.com/csharp/customsinks.asp
75