ceskaexpedice / kramerius

System Kramerius
GNU General Public License v3.0
45 stars 26 forks source link

DNNT jádro za mod_proxy_(ajp | http) - problém s kódováním určitých znaků v URL + další problémy #794

Closed mduda100871 closed 10 months ago

mduda100871 commented 3 years ago

Zdravím,

hlásím problém s jádrem Krameria pro DNNT nasazení - aktuálně verze 5.4.7-dnnt.

Prostředí: OS - Debian GNU/Linux, Buster webserver - distribuční apache z backports, verze 2.4.46 tomcat - distribuční, verze 9.0.31 java - Oracle, verze 8 Shibboleth SP - distribuční, verze 3.0.4

Standardně u všech nasazení Krameria v KNAV používám následující model:

Výše uvedená konfigurace funguje bez problému už roky, nicméně s příchodem DNNT nasazení se objevily komplikace.

Související linky: https://wiki.shibboleth.net/confluence/display/SP3/JavaHowTo https://wiki.cac.washington.edu/pages/viewpage.action?pageId=28939166 https://wiki.shibboleth.net/confluence/display/SP3/ApplicationOverride https://wiki.shibboleth.net/confluence/display/SP3/ApplicationDefaults https://github.com/ceskaexpedice/kramerius/wiki/Shibboleth

Nejprve fragmenty konfigurace DNNT v KNAV:

rdfilter org.apache.catalina.filters.RequestDumperFilter rdfilter /*

....

- Tomcat - `logging.properties` /  konfigurace potřebná pro rozšířené logování atributů v hlavičce, které se předávají do kontejneru z frontendu

....

handlers = ...., 1request-dumper.org.apache.juli.AsyncFileHandler ....

#############################################################

To this configuration below, 1request-dumper.org.apache.juli.FileHandler

also needs to be added to the handlers property near the top of the file

1request-dumper.org.apache.juli.AsyncFileHandler.level = INFO 1request-dumper.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs 1request-dumper.org.apache.juli.AsyncFileHandler.prefix = localhost_access_log.req-dumps. 1request-dumper.org.apache.juli.AsyncFileHandler.encoding = UTF-8

1request-dumper.org.apache.juli.AsyncFileHandler.formatter = org.apache.juli.VerbatimFormatter

1request-dumper.org.apache.juli.AsyncFileHandler.rotatable = false org.apache.catalina.filters.RequestDumperFilter.level = INFO org.apache.catalina.filters.RequestDumperFilter.handlers = \ 1request-dumper.org.apache.juli.AsyncFileHandler #############################################################

....


- Shibboleth SP - `shibboleth2.xml` / konfiurace pouze proti lokálnímu IdP KNAV v testovacím režimu

....

<ApplicationOverride id="kramerius-dnnt" entityID="https://kramerius-dnnt.lib.cas.cz/shibboleth" REMOTEUSER="eppn persistent-id" attributePrefix="AJP"
ciherSuites="DEFAULT:!EXP:!LOW:!aNULL:!eNULL:!DES:!IDEA:!SEED:!RC4:!3DES:!kRSA:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1">

<Sessions lifetime="28800" timeout="10800" relayState="ss:mem" checkAddress="false" handlerURL="/Shibboleth.sso" exportLocation="https://kramerius-dnnt.lib.cas.cz/Shibboleth.sso/GetAssertion" exportACL="127.0.0.1" handlerSSL="true" cookieProps="https">

 <SSO entityID="https://idp.lib.cas.cz/idp/shibboleth">
    SAML2
 </SSO>

 <!-- SAML and local-only logout. -->
    <Logout>SAML2 Local</Logout>

    <!-- Administrative logout. -->
    <LogoutInitiator type="Admin" Location="/Logout/Admin" acl="127.0.0.1 ::1" />

    <!-- Extension service that generates "approximate" metadata based on SP configuration. -->
    <Handler type="MetadataGenerator" Location="/Metadata" signing="false" template="metadata-dnnt-template.xml"/>

    <!-- Status reporting service. -->
    <Handler type="Status" Location="/Status" acl="127.0.0.1 ::1"/>

    <!-- Session diagnostic service. -->
    <Handler type="Session" Location="/Session" showAttributeValues="true"/>

    <!-- JSON feed of discovery information. -->
    <Handler type="DiscoveryFeed" Location="/DiscoFeed"/>

 </Sessions>

 <MetadataProvider type="XML" validate="true" path="idp-metadata-local.xml" legacyOrgNames="true">
    <MetadataFilter type="Whitelist">
       <Include>https://idp.lib.cas.cz/idp/shibboleth</Include>
    </MetadataFilter>
 </MetadataProvider>

 <CredentialResolver type="File" use="signing" key="kramerius-dnnt.lib.cas.cz-key.pem" certificate="kramerius-dnnt.lib.cas.cz-cert.pem"/>
 <CredentialResolver type="File" use="encryption" key="kramerius-dnnt.lib.cas.cz-key.pem" certificate="kramerius-dnnt.lib.cas.cz-cert.pem"/>

....


- Apache - konfigurace virtuálu DNNT pro AJP (mod_proxy_ajp) => **nefunkční předávání autorizačních atributů od SP do kontejneru**
    ....

    <Location "/">
        AuthType shibboleth
        ShibRequireSession Off
        ShibRequestSetting applicationId kramerius-dnnt
        ShibRequestSetting exportStdVars true
        ShibUseHeaders On
        <RequireAll>
            <RequireAny>
               ....
            </RequireAny>
            Require shibboleth
        </RequireAll>
    </Location>

    ....

    <IfModule mod_proxy_ajp.c>

        ProxyPreserveHost On

        ProxyIOBufferSize 65536

       ....

        <LocationMatch "^/search/(.*)$">
            <RequireAll>
               <RequireAny>
                  ....
               </RequireAny>
               Require shibboleth
            </RequireAll>
            ProxyPass "ajp://10.63.0.86:9011/search/$1" retry=5
            ProxyPassReverse "ajp://10.63.0.86:9011/search/$1"
        </LocationMatch>

        .....

     </IfModule>

     ....

- Apache - konfigurace virtuálu DNNT pro HTTP (mod_proxy_http) => **funkční předávání autorizačních atributů od SP do kontejneru**
    ....

    <Location "/">
        AuthType shibboleth
        ShibRequireSession Off
        ShibRequestSetting applicationId kramerius-dnnt
        ShibRequestSetting exportStdVars true
        ShibUseHeaders On
        <RequireAll>
            <RequireAny>
               ....
            </RequireAny>
            Require shibboleth
        </RequireAll>
    </Location>

    ....

    <IfModule mod_proxy_http.c>

        ProxyPreserveHost On

        ProxyIOBufferSize 65536

       ....

        <LocationMatch "^/search/(.*)$">
            <RequireAll>
               <RequireAny>
                  ....
               </RequireAny>
               Require shibboleth
            </RequireAll>
            ProxyPass "http://10.63.0.86:9081/search/$1" retry=5
            ProxyPassReverse "http://10.63.0.86:9081/search/$1"
        </LocationMatch>

        .....

     </IfModule>

     ....

Přestože jsem se snažil postupovat přesně dle doporučení, nepodařilo se mi prostřednictvím AJP protokolu (modul `mod_proxy_ajp` v Apachi) "protlačit" v http hlavičce potřebné autorizační atributy od SP, se kterými pak pracuje samotné jádro Krameria - viz příklad konfigurace Apache pro AJP protokol výše.

Pokud použiji konfiguraci s `mod_proxy_http`, viz příklad konfigurace Apache pro HTTP výše, tak jsou autorizační atributy od SP do kontejneru předány bez problému a jádro Krameria je schopno mapovat role dle obdržených atributů - viz link do dokumentace uvedený výše. Výsledkem je funkční přihlášení do web klienta Krameria, ustavená Shibboleth session a předané autorizační atributy.

Předávání / nepředávání autorizačních atributů na základě použití výše uvedených konfigurací jsem ověřil pomocí logování hlaviček - potřebná konfigurace kontejneru viz výše.

Ustavená session a předané atributy lze (mimo log kontejneru, pokud není aktivní logování hlaviček) kontrolovat na následujích URL:

https:///Shibboleth.sso/Session https:///search/api/v5.0/debug/headers

Příklad hlavičky získané z debug api Krameria (atributy s prefixem `ajp_`):

HTTP Request headers

Mon Feb 08 11:04:02 CET 2021

ajp_preferredlanguage= ajp_title= ajp_vopersonexternalaffiliation= ajp_member= ajp_mail= ajp_shib-session-expires=1612807419 accept=text/html,application/xhtml+xml,application/xml;q\=0.9,image/avif,image/webp,image/apng,/;q\=0.8,application/signed-exchange;v\=b3;q\=0.9 x-forwarded-for=147.231.63.3 ajp_persistent-id=https\://idp.lib.cas.cz/idp/shibboleth!https\://kramerius-dnnt.lib.cas.cz/shibboleth!EtfkUkO5fMA8o6/M1RNFEmOo7O0\= ajp_schacpersonaluniqueid= ajp_shib-authncontext-decl= ajp_street= ajp_initials= ajp_employeenumber= ajp_schacuserstatus= ajp_eppn= ajp_shib-session-index=a4a04629c03dbe3648ebb7176bc8e5f2c5b1fb487fe0313865e6223716da4042 upgrade-insecure-requests=1 ajp_postofficebox= ajp_shib-session-inactivity=1612789442 ajp_seealso= connection=close ajp_nickname= ajp_postalcode= user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 ajp_uid= accept-language=cs-CZ,cs;q\=0.9 ajp_educoursemember= x-forwarded-server=kramerius-dnnt.lib.cas.cz ajp_schachomeorganization= ajp_manager= ajp_cn=XXX ajp_orgunit-dn= ajp_ou= ajp_shib-session-id=_5daa03a432fff89bb7b709c21084c782 ajp_carlicense= ajp_businesscategory= ajp_shib-assertion-count= ajp_telephonenumber= ajp_shib-authentication-method=urn\:oasis\:names\:tc\:SAML\:2.0\:ac\:classes\:PasswordProtectedTransport cookie=JSESSIONID\=F5DD0CD192B6E0E2F3B783BF5FBCBC4F; _ga\=GA1.2.2023506258.1612778549; _gid\=GA1.2.1845531678.1612778549; _shibsession_6b72616d65726975732d646e6e7468747470733a2f2f6b72616d65726975732d646e6e742e6c69622e6361732e637a2f73686962626f6c657468\=_5daa03a432fff89bb7b709c21084c782 ajp_schacprojectspecificrole= ajp_shib-cookie-name= sec-fetch-dest=document ajp_shib-application-id=kramerius-dnnt ajp_physicaldeliveryofficename= ajp_shib-handler=https\://kramerius-dnnt.lib.cas.cz/Shibboleth.sso x-forwarded-host=kramerius-dnnt.lib.cas.cz ajp_facsimiletelephonenumber= ajp_org-dn= ajp_subject-id= ajp_entitlement=urn\:mace\:dir\:entitlement\:common-lib-terms ajp_schacprojectmembership= ajp_displayname= ajp_edupersonuniqueid=XXX ajp_educourseoffering= ajp_primary-orgunit-dn= ajp_schacpersonaluniquecode= sec-fetch-site=none ajp_primary-affiliation= ajp_givenname= accept-encoding=gzip, deflate, br ajp_assurance= ajp_departmentnumber= ajp_st= sec-fetch-user=?1 ajp_affiliation=employee@lib.cas.cz;member@lib.cas.cz;staff@lib.cas.cz ajp_sn= ajp_shib-authentication-instant=2021-02-08T10\:03\:38.902Z remote_user=https\://idp.lib.cas.cz/idp/shibboleth!https\://kramerius-dnnt.lib.cas.cz/shibboleth!EtfkUkO5fMA8o6/M1RNFEmOo7O0\= ajp_shib-identity-provider=https\://idp.lib.cas.cz/idp/shibboleth ajp_shib-authncontext-class=urn\:oasis\:names\:tc\:SAML\:2.0\:ac\:classes\:PasswordProtectedTransport ajp_pairwise-id= ajp_description= ajp_employeetype= host=kramerius-dnnt.lib.cas.cz sec-fetch-mode=navigate ajp_o=KNAV ajp_unscoped-affiliation= ajp_l= ajp_schachomeorganizationtype=


To je celkem nepříjemné, protože AJP je dle dokumentace preferovanější / doporučené.

To je tedy první část problému. Pokud má někdo funkční konfiguraci SP + Apache AJP + Tomcat aby to předávalo autorizační atributy, tak je vítán pokud se podělí. Mě se to po několikadenním zkoumání a laborování nepodařilo "prorazit".

Změnil jsem tedy konfiguraci na HTTP `mod_proxy_http`. Všechno začalo chodit a vypadalo to dobře do doby, než jsem se přihlásil do jádra DNNT Krameria a chtěl tam provádět nějaké konfigurační akce.

No a tady jsem narazil - to je druhá část problému.

Jádro DNNT Krameria nemá ošetřené některé speciální znaky předávané v URL jako to má oficiální, neDNNT, vývojová větev jádra Krameria, aktuálně ve verzi 5.5.0.

Konkrétně nejde například vyvolat formulář pro editaci/konfiguraci akce tlačítkem [Editovat] z formuláře `Globální akce`. Jde právě o ty výše zmíněné speciální znaky v URL, v tomto případě konkrétně o složené závorky `{}`.

Pod tlačítkem [Editovat] u akce "Číst" se skrývá konkrétně následující volání - **platí pro neDNNT jádro Krameria 5.5.0**:

https:///search/inc/admin/_display_rights_for_globalactions.jsp?pids=%7Buuid%5C%3A1%7D&securedaction=read&=1612768842322

kde je vidět, že složené závorky `{}` u atributu `pids=<závorka>uuid` jsou nahrazeny kódem `%7B`, resp. `%5C`. Takto ošetřené to projde jak přes AJP proxy, tak i přes HTTP proxy.

Nicméně jádro DNNT Krameria 5.4.7-dnnt toto dle všeho ošetřené nemá (vychází ze starší větvě zdrojových kódů kde to ošetřené ještě nebylo - informace přímo od vývojáře), takže výše zmíněný pokus s editací končí chybou `400 - bad request` na straně Tomcatu.

Ten totiž dostane následující request:

https:///search/inc/admin/_display_rights_for_globalactions.jsp?pids={uuid\:1}&securedaction=read&=1612768923604


kde je krásně vidět, že závorky nejsou ošetřené. Pokud se toto "prožene" přes AJP proxy, tak to Tomcat přijme a chyba nenastane. Pokud to jde ale přes HTTP proxy, tak to kolabuje.

Stejný problém se vyskytl ještě u vyvolání formuláře pro nastavení parametrů procesu pro nastavení DNNT příznaku. Dále jsem se nedostal, ale lze předpokládat, že pokud jsou takto neošetřená i další volání, bude to nefunkční i jinde v jádře DNNT Krameria.

Je to trošku takový "deadlock" - není možné použít AJP proxy (problémy s atributy od SP - uvedeno výše)  a s HTTP proxy sice funguje přihlašování, ale zase nejde spravovat aplikace.

Zatím jsem to obešel tak, že mám dva webservery. Jeden jako frontend pro webklienta + SP, který s jádrem DNNT Krameria na backendu komunikuje prostřednictvím HTTP proxy, a druhý jako backend na jiném serveru, který komunikuje s jádrem DNNT Krameria prostřednictvím AJP proxy abych to byl schopen nějak administrovat.

Je to ovšem nedobrá situace. Je potřeba opravit výše zmíněná volání v jádře DNNT Krameria (kódování speciálních znaků) aby to procházelo i přes HTTP proxy a Tomcat to byl schopen akceptovat.

A vůbec nejlepší by bylo provést merge DNNT rozšíření do hlavní větvě Krameria (5.5.0) a nadále už udržovat jen a pouze jednu větev. Výše zmíněné problémy vznikly dle všeho právě díky rozštěpení vývoje a zaostání v jedné z nich v aplikovaných opravách.

Tady je ještě rychlý "workaround", ale to není příliš systémové - oprava by měla být proveden v jádře Krameria:

https://github.com/ceskaexpedice/kramerius/issues/470

zatím nemám otestováno, chystám se.

Díky, MD
mduda100871 commented 3 years ago

Potvrzeno - přidání atributu relaxedQueryChars="{}\" do konfigurace Tomcatu pomůže. Je tedy potřeba opravit DNNT jádro.

Problém s předáváním autorizačních atributů prostřednictvím AJP proxy to samozřejmě neřeší.

MD

pavel-stastny commented 10 months ago

@mduda100871 Popsané nastavení pro K5 funguje, pro K7 již není potřeba. Issue zavírám.