Unngå å dra med deg bakdøra i produksjon med Maven

Når man lager sikker og robust programvare er det ofte en utfordring å teste applikasjonen under utvikling. Man lager derfor såkalte bakdører for å slå av, eller omgå, sikkerhet. Dette kan være nyttig og tidsbesparende for utviklere og testere mens man utvikler eller tester det man utvikler. Men, det er imperativt at ikke slik kode blir med ut i produksjon på en måte som gjør at utenforstående kan utnytte disse bakdørene for “ondsinnede” angrep. Ofte ser man teknikker hvor utviklere gjør det vanskelig for utenforstående å finne eller aktivere disse bakdørene, men det tryggeste, og etter min mening det eneste riktige, er å utelate slik kode i produksjon.

Nylig utviklet vi en web-applikasjon i Java med flere nivåer av autentisering. Vi benyttet Jetty som container og Maven 2 for bygging og pakking av applikasjonen. Vi lagde en bakdør for enkelt å kunne autentisere og autorisere oss på forskjellige nivåer. Slik ungikk vi å måtte logge oss på hver gang vi ønsket å teste noe vi nettopp hadde utviklet. Når det nærmet seg første produksjonssetting måttte vi for enhver pris unngå at denne bakdøren ble med ut i produksjon. Vi ønsket imidlertid ikke å forkaste løsningen da den ga oss en mye kjappere utviklingssyklus (kode-kjøre-teste, kode-kjøre-teste…) og applikasjonen skulle selvsagt videreutvikles. Vi løste det på følgende måte.

Bakdøra

Vi hadde et servletfilter som het BackDoorFilter. Denne skrur av sikkerhet i applikasjonen hvis systemvariabelen dev.mode er satt til true ved å autentisere med ønsket sikkerhetsnivå i sesjonsobjektet. I tillegg lagde vi en jsp-side med en meny hvor man kunne velge hvilken bruker og hvilken autorisering denne skulle autentiseres med.

For å være helt sikre utelot vi BackdoorFilter.class og JSP-en backdoor.jsp fra den pakkede webapplikasjonen (war-filen) dersom man ikke bygger med profilen dev. I tillegg utelot vi konfigurasjonen av disse i web.xml ved å filtrere deployment-descriptoren (web.xml) ved bygging. Dersom vi aktiverer profilen dev ved bygging pakkes bakdøren med applikasjonen, og konfigurasjonen av bakdøren filtreres inn i web.xml. Dette løste vi ved å konfigurere maven-war-plugin med packagingExcludes og filteringDeploymentDescriptors=true.

pom.xml

web.xml

I web.xml legges placeholdere til for verdiene som skal filtreres inn (backdoor.filter, backdoor.filter-mapping og devInnlogging.servlet).

Vi måtte gjøre en ting til for å få jetty:run til å fungere tilsvarende

Maven-jetty-plugin er en fabelaktig plugin som gir en kjapp utviklingssyklus. Med denne kan man kjøre mvn jetty:run og få opp en kjørende versjon av applikasjonen din som støtter hot deploy (med visse unntak som jeg ikke vil komme inn på her). Maven-jetty-plugin kan kjøre på en utpakket versjon av applikasjonen din og re-laste applikasjonen med jevne mellomrom (i dette tilfellet hvert tiende sekund). Det betyr at man kan endre kildekode mens applikasjonen kjører og man vil se resultatene (nesten) umiddelbart. Utfordringen er at den ikke tar hensyn til maven-war-plugin, og dermed vil ikke oppsettet jeg har beskrevet ovenfor fungere. Dermed måtte vi løse dette på en lignende, men separat måte. Vi ønsket ikke å lage en separat web.xml, da dette ville medføre unødvendig duplisering. Vi tok derfor utgangspunkt i samme web.xml og filtrerte denne som en maven resource. Pluginen build-helper-maven-plugin lar oss legge til nye resource-kataloger i tillegg til default src/main/resources og src/test/resources.

pom.xml

Oppsummert

Bakdører kan være svært nyttig under utvikling og testing, men man må være forsiktig slik at disse ikke blir med ut i produksjon. Derfor bør man utelate slik kode ved normal bygging av applikasjonen. Under utvikling kan man lage en dev-profil i Maven som inkluderer bakdøren ved aktivering:

  • mvn clean install -Pdev
  • mvn jetty:run -Pdev
  • http://www.johannesbrodwall.com/ Johannes Brodwall

    Jetty er en bra app server som skinner spesielt ved testing. Men jetty-maven-plugin er en blindvei til en mer effektiv hverdag. Som mange andre har du støtt på et sted der det som burde være enkelt blir komplisert på grunn av jetty-maven-plugin.

    Så lenge det du ønsker å gjøre er å kjøre en lokal server for testing, er det mye bedre å lage en main klasse som instansierer org.eclipse.jetty.server.Server (for Jetty 7). Følgende skal til for å instansiere web-contexten og legge til et EKSTRA filter:

    WebAppContext webApp = new WebAppContext(“src/main/webapp”, “/my-web-app”);
    webApp.addFilter(MyBackdoorFilter.class.getName(), “*”, EnumSet.of(DispatcherType.REQUEST));
    server.setHandler(webApp);

    Slik slipper du å generere web.xml dynamisk.

    Dersom klassen ligger i test-classpath, kan også filteret ligge i test-classpath. Slik slipper du å ekskludere filteret eksplisitt under bygging.

    Kom deg ut av jetty-maven-plugin fella. :-)

    • Stein Inge Morisbak

      Takk for tipset Johannes :-) Vi kjører faktisk Jetty i en main klasse i et eget sub-prosjekt, men har foreløpig mesteparten av konfigurasjonen i xml i web-prosjektet. Skal definitivt ta en titt på dette. Virker som en bedre løsning enn å modifisere xml-fila ved kjøring av Jetty lokalt. Håper imidlertid oppskriften kan brukes for de som bruker andre web-containere eller ikke kjører embedded. Vi ønsker dessuten å deploye bakdøra til test- og utviklingsmiljøer, ikke kun for testing lokalt.

      • http://www.johannesbrodwall.com/ Johannes Brodwall

        Det er alltid bra å ha flere teknikker for å løse problemer, så det er et nyttig knep å ha i ermet.

        På det forrige prosjektet mitt brukte vi Jetty med en bakdør på vår arbeidsstasjon for utvikleres test og deployet på OC4J (ick!) uten bakdøren for produkteiers test. De eneste feilene vi opplevde mellom utvikling og test var knyttet til autensieringen (altså bakdøren) eller dramatiske ting som classpath-forskjeller.

        Så min erfaring er at det er lurt å gjøre så mye som mulig av testingen i Jetty og med mindre de eksterne testerne er helt avhengig av bakdøren, ville jeg fortrukket at de har samme artifakt som den som skal i prod.

  • http://morisbak.net/people/stein Stein Inge Morisbak

    En ting til; hovedpoenget med å gjøre det på måten jeg har beskrevet er at vi ønsket å deploye bakdøra til test- og utviklingsmiljøer også. Ikke bare for kjøring lokalt.

    Ellers er jeg enig med Johannes i at jetty-maven-plugin ikke er optimalt for lokal kjøring. Mye mindre kontroll på konfigurasjon enn ved “håndkodet” variant.

  • Orby

    Har dere noe erfaring med JRebel istedenfor Jetty?

    • http://www.johannesbrodwall.com/ Johannes Brodwall

      Jeg har veldig lite erfaring med JRebel, men den er ikke et applikasjonsserver/webserver. Den er kun for å aksellerere restart-syklusen for en JVM og kan gjerne brukes sammen med Jetty.

      Når det er sagt, hadde jeg når jeg prøvde JRebel problemer med å takle Hibernate-endringer og Hibernate var det eneste som tok tid ved restart, så…

      • Orby

        Du har selvfølgelig helt rett, JRebel i seg selv er ingen applikasjonserver/webserver, bare blitt en vane for meg å omtale det slik da jeg “starter” JRebel og ikke Tomcat. Det vil si, jeg starter Tomcat med JRebel :-)

        Vi har ihvertfall gått bort i fra Jetty og redeploy. JRebel fungerer fantastisk bra. Rediger en javaklasse, css, javascript, jsp, oppdaterer nettleseren og du ser dine endringer :-)

        Har ikke brukt det med Hibernate, men bruker det mye med Struts, Spring og iBatis.