Disassembler

Artificial intelligence is no match for natural stupidity.
13listopadu2016

IIS jako reverzní proxy pro Tomcat


To jsem se zas jednou nachomýtnul k hurá akci. Že já vůl taky všem na všechno kývnu. To si tak zákazník vymyslel, že když teda jako skončila podpora pro Windows 2003 (před více než rokem!), tak si upgraduje. Windows Server 2012 by pro něj byly asi moc velký technologický skok nebo co, takže si přelezl na 2008. Společně s tím si přetáhl aplikaci běžící na Tomcatu a až pan Košťál mávnul, kapela začala hrát a pštros byl vypuštěn do produkce, zjistilo se, že to funguje nějak divně. On totiž na nových serverech nikdo nenainstaloval IIS, které mělo plnit funkci reverzní proxy a starat se o redirecty, NTLM autentizaci a podobné maličkosti.

Kocourkov


Samotná javovská aplikace běží na Tomcatu 6, ale níže popsané akce a metody budou fungovat prakticky s jakýmkoliv aplikačním serverem nebo JSP/servlet kontejnerem komunikujícím skrze AJP protokol. Princip je stejný jako u nastavení jakýchkoliv jiných reverzních proxy. Do webového serveru se zavede modul, který umí překládat požadavky do příslušného protokolu aplikačního serveru a pak se v konfiguraci tohoto modulu definují pravidla pro URL, aby bylo jasné, jaké kontexty se mají předávat jaké aplikaci. Nejprve je samozřejmě potřeba do provozuschopného stavu uvést samotný Tomcat. Tady v celku žádná zrada není. Nastavení v server.xml může zatím zůstat ve výchozím stavu, a pokud jsou nainstalovány i Tomcatí examples aplikace, návštěva URL http://localhost:8080/examples/ vás dovede k jednoduchému rozcestníku. Kontext /examples/ tedy bude představovat moji aplikaci, na kterou se budu chtít dostat skrze IIS. To se sice lehko řekne, ale v případě IIS je tu pár klacků hozených pod nohy, které bylo potřeba překonat. Dokumentace Tomcatu, respektive jeho redirectoru pro IIS je v tomto směru trochu zmatená. Například zmiňované zásahy do registrů vůbec nejsou potřeba, což se tam sice píše, ale už se tam nepíše co všechno je potřeba nastavit na straně IIS. Nicméně ze střípků se dá poskládat, jak že by to nastavení vlastně mělo vypadat.

Instalace IIS


Mám anglické Windows Server 2008 R2, takže názvy budu popisovat anglicky. Pokud jste takoví dobrodruzi, že instalujete servery v regionálních jazykových mutacích, nic není ztraceno, protože překlady jsou poměrně jasné, akorát se s nimi budete muset poprat sami.

Celkem nepřekvapivě je na Windows Serveru potřeba přiřadit roli Web Server (IIS). U této role je nutno zapnout několik featur, které zajistí, že se IIS s Jakarta redirectorem bude kamarádit a že bude podporovat i ostatní požadovaná nastavení. Výchozí výběr tedy ponechte a přidejte

Web Server > Application Development > ISAPI Extensions
Web Server > Application Development > ISAPI Filters
Web Server > Security > Windows Authentication

95 % webových serverů, se kterými pracuji, jsou Apache. Proto mě celkem překvapilo, že IIS neumí out-of-the-box takovou pitomost jako přepisování (rewrite) URL nebo přesměrování (redirect) na základě sady pravidel. Sice umí HTTP Redirect, ale ten prostě šmahem vezme všechno, co mu přijde pod ruku, a pošle to někam pryč. Pro přesměrování kořenového kontextu na aplikační kontext (tedy http://server/ -> http://server/examples/) je třeba doinstalovat ještě IIS URL Rewrite Module. Ten se dá získat přímo u zdroje, případně nainstalovat skrze Web Platform Installer. Šup tam s ním.

Indonéská spojka


Java, Jakarta... jeden by si myslel, že tu plánuju dovolenou, ale to se nám jen tak hezky sešly názvy. Ono konec konců najít u Apache Foundation nějaké šílenosti není nic neobvyklého. Ant, Camel, Jackrabbit, Pig, Tomcat... není divu, že mají i ZooKeepera. Mám pocit, že porady, na kterých se rozhoduje o názvech produktů, mají formu stand-up comedy. Ale zpátky k tématu. Použitý redirector je v tomto případě isapi_redirect.dll stažený rovnou jako zkompilovaná binárka. Už tady je nastražená první past, protože IIS 6 na Win2003 vyžaduje 32bitovou variantu, ale IIS 7, resp. 7.5 na Win2008 už jsou 64bitové. Stáhněte tedy příslušnou variantu, vytvořte adresář C:\inetpub\jakarta a rozbalte do něj z archivu výše zmíněný isapi_redirect.dll. Dále v tomto adresáři ještě vytvořte podadresář logs, u kterého ve vlastnostech zabezpečení povolte měnit a zapisovat skupině Users, jinak se vám nebudou vytvářet logy.

Poté stále v adresáři jakarta vytvořte soubor isapi_redirect.properties a naplňte zhruba následujícím obsahem.

extension_uri=/jakarta/isapi_redirect.dll

log_file=C:\inetpub\jakarta\logs\isapi_redirect.%Y-%m-%d.log
log_level=info
log_rotationtime=86400

worker_file=C:\inetpub\jakarta\workers.properties
worker_mount_file=C:\inetpub\jakarta\uriworkermap.properties

První řádek je celkem jasný. Říká, která knihovna se má použít pro zpracování požadavků. Logovací direktivy na dalších třech řádcích nastavují název souboru a zajistí, že redirector bude vytvářet log pro každý den odděleně. Poslední dva řádky už jsou trochu zajímavější, protože oba odkazované soubory je nutno vytvořit. Soubor workers.properties nastavuje parametry vláken zajišťujících spojení a předávání požadavků mezi IIS a Tomcatem, takže v něm bude řečeno, kde sedí a poslouchá AJP port Tomcatu. Také je zde definován worker řešící logování a hlídání stavu.

worker.list=ajp13,jkstatus

worker.ajp13.type=ajp13
worker.ajp13.host=localhost
worker.ajp13.port=8009

worker.jkstatus.type=status

Soubor uriworkermap.properties pak nastavuje kontexty, které jsou obsluhovány jednotlivými workery a které mají být předávány aplikacím běžícím v Tomcatu (podobně jako JkMount u mod_jk pro Apache). Nastavuji aplikaci s kontextem /examples/ a také můžu nastavit diagnostickou URL /jkmanager, abych mohl koukat na stav workerů. Tu ale na produkci určitě nedávejte nebo si ji nějak pořádně zabezpečte. Soubor bude tedy mít následující obsah.

/examples/*=ajp13
/jkmanager=jkstatus

Tím konfigurace redirectoru končí. To byla, alespoň podle mého, ta jednodušší část.

Pain in the IIS


Zahřejte myš, bude se klikat. Nejprve je třeba v Internet Information Services (IIS) Manager milý isapi_redirect.dll vůbec povolit spouštět. To provedete ve Features View v kontextu celého serveru volbou ISAPI and CGI Restrictions a následným kliknutím na Add... v pravém panelu. Vyplňte následující hodnoty a potvrďte kliknutím na OK.

ISAPI or CGI path: C:\inetpub\jakarta\isapi_redirect.dll
Description: Apache Tomcat Jakarta connector
Allow extension path to execute: zaškrtnout (zapnout) checkbox

ISAPI and CGI Restrictions

Dále se dá pokračovat několika způsoby. Buď se ISAPI filtr a pár dalších nastavení provede na úrovni celého serveru nebo pouze pro příslušný web. Pak je tu ještě varianta, při které se nastavení ISAPI filtrů zapisuje i do souboru web.config v kořenovém adresáři webu, takže je možno odnést web i s celým nastavením. Ale v takovém případě je potřeba tuto skutečnost IIS napřed skrze Configuration Editor sdělit, a požádat jej o odemknutí sekcí system.webServer/isapiFilters a system.webServer/handlers. Já na svém serveru měl pouze jedinou aplikaci, takže jsem nic takového dělat nepotřeboval a nastavení ISAPI filtru jsem provedl rovnou v kontextu celého serveru. V ISAPI Filters opět klikněte na Add... a přidejte následující.

Filter name: Apache Tomcat Jakarta connector 
Executable: C:\inetpub\jakarta\isapi_redirect.dll

ISAPI Filters

Pak ještě, stále ve Features View v kontextu serveru, dvojklikněte na Handler Mappings, v pravém panelu vyberte Edit Feature Permissions... a zatrhněte (zapněte) i zbývající Execute.

Handler Mappings

Globální nastavení IIS je hotovo a můžeme se vrhnout na samotnou aplikaci. Výchozí Default Web Site jsem smazal (což ale není povinný krok) a místo toho si pravým klikem na Sites a vybráním Add Web Site... vytvořil novou. Název sítě a hostname si přizpůsobte svým potřebám. Fyzická cesta by v tomto případě měla ukazovat do adresáře se statickými soubory, které mají být vybavovány přímo IIS, ale já žádné nemám, takže jsem cestu práskl tam, kde sídlí isapi_redirect.dll a jeho parta.

Site name: Examples
Physical path: C:\inetpub\jakarta
Host name: localhost

Add Web Site

Poté na nově vytvořený web ve stromečku vlevo opět klikněte pravým myšítkem a vyberte Add Virtual Directory.... Opět vyplňte následující údaje, přičemž jméno virtuálního adresáře, resp. relativní cesta k isapi_redirect.dll se v tomto případě musí shodovat s tím, co se píše na prvním řádku v isapi_redirect.properties.

Alias: jakarta
Physical path: C:\inetpub\jakarta

Add Virtual Directory

Přemýšlivý čtenář možná namítne, že pokud mám pouze jedinou prezentaci a nemám žádné statické soubory, mohl jsem si udělat adresář pro web, který by měl jakarta přímo jako fyzický podadresář a nemusel se obtěžovat s virtuálním adresářem. Ano, fungovalo by to, ale best practice je virtuální adresář. V případě, že statické soubory přibydou, nebudu muset přestavět půlku konfigurace. Stejně tak, pokud přibude další prezentace, bylo by nežádoucí knihovnu a všechny properties soubory duplikovat.

Pak už jen otočte IIS, buď přímo z IIS manažera, nebo ze správce služeb a první část je za námi. Aplikace by teď měla být dostupná i na http://localhost/examples/.

Our princess is in another castle


Teď bych rád, aby má aplikace byla dostupná přímo po zadání pouhého http://localhost/ nebo alespoň dle původního zadání bylo http://localhost/ přesměrováno na http://localhost/examples/. Jak jsem zmínil výše, featura HTTP Redirect, kterou IIS obvykle poskytuje, by v tomto případě nedostačovala, protože chceme přesměrovávat pouze jedinou konkrétní (prázdnou) URL a ne úplně všechno. Záležitost, která je na Apache HTTP Serveru otázkou dvou řádků (tří, pokud počítám i nahrání mod_rewrite) a kterou jsem dělal už tisíckrát, mi na IIS pořádně napálila kudrlinku. Samotné pravidlo je v principu stejné. Potřebuju redirectovat vše, co odpovídá regulárnímu výrazu ^$ na http://localhost/examples/. To se dá vyklikat i přes IIS Manager. Otevřete kontext webu Examples, dvojklikněte na URL Rewrite featuru, vpravo zvolte Add Rule(s)... a pak Blank rule a OK. Do polí vyplňte následující hodnoty a pak klikněte na Apply.

Name: Root context redirect
Pattern: ^$
Action type: Redirect
Redirect URL: http://localhost/examples/

URL Rewrite (Redirect)

Nastavení se projeví okamžitě. Bohužel se ale projeví tak, že celý web začne zobrazovat chybu

HTTP Error 500.52 - URL Rewrite Module Error.
The page cannot be displayed because an internal server error has occurred.

Module: RewriteModule 
Notification: SendResponse 
Handler: ISAPI-dll 
Error Code: 0x800700b7 
Config Error: Cannot add duplicate collection entry of type 'rule' with unique key attribute 'name' set to 'Root context redirect'  
Config File: \\?\C:\inetpub\jakarta\web.config

Nejužitečnější část bude patrně popis v Config Error. Milé IIS se z nějakého důvodu snaží s každým vnořeným adresářem v URL ono pravidlo opakovaně přidávat do sady již existujících pravidel. Také si můžete povšimnout, že je zmíněn soubor C:\inetpub\jakarta\web.config. To je lokální konfigurace v kořenovém adresáři webu, která obsahuje onen nově vytvořený redirect. Řešení problému je naštěstí jednoduché jak facka, kterou by si mimochodem programátor tohoto modulu zasloužil. Onu problematickou sadu je totiž možno před každým zpracováním vyprázdnit, ale to už se nedá udělat přes GUI. Otevřete tedy C:\inetpub\jakarta\web.config v editoru a změňte jeho obsah na

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <clear />
                <rule name="Root context redirect" stopProcessing="true">
                    <match url="^$" />
                    <action type="Redirect" url="http://localhost/examples/" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

Vidíte tam to malé <clear />? Tak to mě stálo hodinu a půl času. Doufám, že vám to nějaký ušetří.

NTLM Autentizace


Jelikož jsem samozřejmě jenom lopata s bílým límečkem, přihlašovací údaje k aplikaci mi nedali, takže veškeré autentizační a autorizační mechanismy jsem si nechal na konec. Aplikace, ke které nemáte přístup, se konfiguruje a testuje celkem blbě. Na rozdíl od všech doposud provedených úprav se NTLM (nebo jakákoliv jiná externí) autentizace týká i přímo Tomcatu. V server.xml je nutno u AJP konektoru nutno explicitně povolit příjem těchto informací parametrem tomcatAuthentication="false". Ve výchozím stavu totiž Tomcat okázale ignoruje jakékoliv autentizační hlavičky a předpokládá, že si autentizaci bude řešit sám. Otevřete tedy tomcat.xml a řádek s definicí AJP konektoru změňte na něco podobného.

<Connector port="8009" redirectPort="8443" protocol="AJP/1.3" tomcatAuthentication="false" />

Nezapomeňte pak Tomcat restartovat s novým nastavením.

Na straně IIS je pak nastavení NTLM až trapně jednoduché. Aby taky ne, když je společně s IIS z jednoho hnízda. V IIS Manageru vyberte web Examples a ve Feature View poklepejte na Authentication. Zde vyberte Windows Authentication a v pravém panelu jej pomocí Enable aktivujte. V závislosti na tom, jak je aplikace napsaná a jestli čeká explicitně NTLM hlavičky nebo si umí poradit i jinak, můžete stejným způsobem chtít vypnout ostatní metody autentizace.

NTLM Autentizace

Na konec ještě jednou pro jistotu otočte server a máte hotovo.

Apache HTTP Server


Jen tak pro zajímavost.

LoadModule jk_module modules/mod_jk.so
JkWorkersFile /etc/httpd/conf/workers.properties
JkMount /examples/* ajp13

LoadModule rewrite_module modules/mod_rewrite.so
RewriteEngine On
RewriteRule ^/$ /examples/ [R]

LoadModule authn_ntlm_module modules/mod_authn_ntlm.so
AuthType SSPI
NTLMAuth On
NTLMAuthoritative On
Require sspi-group "DOMENA\APPUSERS"

Nevím, možná jsem divnej, ale tak nějak mi tohle připadá daleko jasnější, jednodušší a přenositelnější.