@startuml autonumber footbox off participant Browser as B participant UI as A participant Knox as G participant SAML_IdP as E B->A: GET(ui-origin-url) note right: User/browser makes request to UI without valid token activate A A-->B: redirect(knox-sso+ui-origin-url) note right: AuthFilter in UI detects no/invalid token redirects to\nKnoxSSO preserving ui-origin-url deactivate A B->G: GET(knox-sso+ui-origin-url) note right: Browser follows redirect activate G G-->B: redirect(idp-login-ui) note right: KnoxSSO finds no/invalid token, redirects to SAML IdP deactivate G B->E: POST(idp-login-ui) note right: Browser follows redirect activate E E-->B: ok(idp-login-ui) note right: SAML IdP presents login form to user deactivate E B->E: POST(idp-login-ui,credentials) note right: User provides credentials to IdP via login form.\nSAML IdP validates credentials. activate E E-->B: redirect(knox-sso,saml-assertion) note right: IdP redirects back to knox-origin-url with SAML assertion\nin form POST deactivate E B->G: POST(knox-sso,saml-assertion) note right: KnoxSSO converts SAML assertion to a KnoxSSO cookie\nand extracts ui-origin-url from original-url cookie activate G G-->B: redirect(ui-origin-url,knox-token) note right: KnoxSSO redirects client back to ui-origin-url with KnoxSSO cookie deactivate G B->A: GET(ui-origin-url,knox-token) note right: Browser follows redirect to ui-origin-url with JWT Bearer Token in cookie.\nJWT Bearer Token validated by AuthFilter in UI activate A A->B: ok(ui-cookie) note right: Request processes and response returned to client. deactivate A @enduml |
The picketlink federation provider allows for the federation of an authentication event that is represented by a SAML assertion cookie/token.
It enables the flow illustrated above in the sequence diagram for SAML based authentication for Hadoop Web UIs and is based on Apache Picketlink.
It has currently been tested with shibboleth as the SAML provider.
The following table details the configuration elements of the provider and reflects the current implementation in the master branch:
| Param | Description | Default |
|---|---|---|
| identity.url | The URL to redirect incoming requests that do not contain the expected cookie and presumably to facilitate an authentication challenge. | none |
| service.url | The URL back to the KnoxSSO endpoint for the IdP to redirect the browser after authentication. | none |
| keystore.url | The location of the keystore with the public cert of the IdP for token validation. BUG: this is currently hardcoded to gateway.jks | gateway.jks |
| validating.alias.key | This is the idp domain which is used as the alias for looking up the alias for the cert to validate incoming tokens with - ie. idp.example.com | none |
| validating.alias.value | This is the alias for the actual cert to use for the idp domain - ie. servercert - and is used by picket link to get to the public key for the IdP in order to verify the presented token. | none |
| clock.skew.milis | The clock skew to use during the validation of tokens | none |
Sample Topology file: "idp.xml"
<topology>
<gateway>
<provider>
<role>federation</role>
<name>Picketlink</name>
<enabled>true</enabled>
<param>
<name>identity.url</name>
<value>https://localhost:9443/idp/profile/SAML2/POST/SSO</value>
</param>
<param>
<name>service.url</name>
<value>http://c6401.ambari.apache.org:8888/gateway/idp/knoxsso/</value>
</param>
<param>
<name>keystore.url</name>
<value>/usr/hdp/current/knox-server/data/security/keystores/gateway.jks</value>
</param>
<param>
<name>validating.alias.key</name>
<value>c6401.ambari.apache.org</value>
</param>
<param>
<name>validating.alias.value</name>
<value>servercert</value>
</param>
<param>
<name>clock.skew.milis</name>
<value>2000</value>
</param>
</provider>
<provider>
<role>identity-assertion</role>
<name>Default</name>
<enabled>true</enabled>
</provider>
<provider>
<role>authorization</role>
<name>AclsAuthz</name>
<enabled>true</enabled>
</provider>
</gateway>
<service>
<role>KNOXSSO</role>
<param>
<name>sso.cookie.secure.only</name>
<value>false</value>
</param>
</service>
</topology>
The following is an example of what the resulting picketlink.xml file should look like in the generated webapp deployments directory:
<?xml version="1.0" encoding="UTF-8" ?>
<PicketLink xmlns="urn:picketlink:identity-federation:config:2.1">
<PicketLinkSP xmlns="urn:picketlink:identity-federation:config:1.0"
ServerEnvironment="jetty"
BindingType="POST"
IDPUsesPostBinding="true"
SupportsSignatures="true"
CanonicalizationMethod="http://www.w3.org/2001/10/xml-exc-c14n#">
<IdentityURL>https://localhost:8443/idp/profile/SAML2/POST/SSO</IdentityURL>
<ServiceURL>http://localhost:9443/gateway/idp/knoxsso/api/v1/websso</ServiceURL>
<KeyProvider ClassName="org.picketlink.identity.federation.core.impl.KeyStoreKeyManager">
<Auth Key="KeyStoreURL" Value="/Users/larry/Projects/incubator-knox/install/knox-0.7.0-SNAPSHOT/data/security/keystores/gateway.jks"/>
<Auth Key="KeyStorePass" Value="knox"/> // populated automatically to match the master secret - must be made configurable though
<Auth Key="SigningKeyAlias" Value="gateway-identity"/> // hardcoded to be gateway identity since it assumes the gateway identity key is used to sign the token
<Auth Key="SigningKeyPass" Value="knox"/> // populated automatically to match the master secret - must be made configurable though
<ValidatingAlias Key="localhost" Value="localhost"/>
</KeyProvider>
</PicketLinkSP>
<Handlers>
<Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler">
<Option Key="NAMEID_FORMAT" Value="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<Option Key="CLOCK_SKEW_MILIS" Value="2000"/>
<Option Key="ASSERTION_SESSION_ATTRIBUTE_NAME" Value="org.picketlink.sp.assertion"/>
</Handler>
<Handler class="org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler"/>
</Handlers>
</PicketLink>
The following notes reflect the CURRENT POC state for the above flow inside an Ambari managed ambari-vagrant 3 node cluster:
* ISSUE: the original-url cookie is currently problematic. We need to utilize the Relay-State of the SAML form elements instead. We then need to intercept it before the redirect filter is invoked and make it available in the request context. This will normalize the redirect mechanism across authentication server providers. For example: SAML, Form, HTTP Basic, etc. The redirect filter can be reused across all of those as long as the original-url can be found the same way.
** CAVEAT: the simple hadoop-auth cookie and any subsequent JWT solution will dictate that knoxsso endpoint be in the same domain as all of the UIs. IOW - all nodes in the cluster that host UIs or will need the cookie to be available need to be in the same domain as the knoxsso endpoint.
OBSOLETE but in the proper spirit of HADOOP-11717 ()
<property>
<name>hadoop.http.authentication.simple.anonymous.allowed</name>
<value>false</value>
</property>
<property>
<name>hadoop.http.authentication.type</name>
<value>org.apache.hadoop.sso.poc.RedirectAuthenticationHandler</value>
</property>
<property>
<name>hadoop.http.authentication.authentication.provider.url</name>
<value>http://c6401.ambari.apache.org:8888/knoxsso</value>
</property>
<property>
<name>hadoop.http.filter.initializers</name>
<value>org.apache.hadoop.security.AuthenticationFilterInitializer</value>
</property>
<property>
<name>hadoop.http.authentication.public.key.pem</name>
<value>MIICVjCCAb+gAwIBAgIJAPPvOtuTxFeiMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV
BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZI
YWRvb3AxDTALBgNVBAsTBFRlc3QxIDAeBgNVBAMTF2M2NDAxLmFtYmFyaS5hcGFj
aGUub3JnMB4XDTE1MDcxNjE4NDcyM1oXDTE2MDcxNTE4NDcyM1owbTELMAkGA1UE
BhMCVVMxDTALBgNVBAgTBFRlc3QxDTALBgNVBAcTBFRlc3QxDzANBgNVBAoTBkhh
ZG9vcDENMAsGA1UECxMEVGVzdDEgMB4GA1UEAxMXYzY0MDEuYW1iYXJpLmFwYWNo
ZS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMFs/rymbiNvg8lDhsdA
qvh5uHP6iMtfv9IYpDleShjkS1C+IqId6bwGIEO8yhIS5BnfUR/fcnHi2ZNrXX7x
QUtQe7M9tDIKu48w//InnZ6VpAqjGShWxcSzR6UB/YoGe5ytHS6MrXaormfBg3VW
tDoy2MS83W8pweS6p5JnK7S5AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEANyVg6EzE
2q84gq7wQfLt9t047nYFkxcRfzhNVL3LB8p6IkM4RUrzWq4kLA+z+bpY2OdpkTOe
wUpEdVKzOQd4V7vRxpdANxtbG/XXrJAAcY/S+eMy1eDK73cmaVPnxPUGWmMnQXUi
TLab+w8tBQhNbq6BOQ42aOrLxA8k/M4cV1A=</value>
</property>
Picketlink POC ServerThe knoxsso endpoint at this point is represented by a simple embedded jetty server hosting a webapp with a number of filters.
It checks for the incoming original-url and sets it in appropriate state for later retrieval, does the SAML dance with the picketlink SPFilter and finally redirects back to original-url.
Built with "mvn clean install".
Run with "mvn exec:java -Dexec.mainClass="org.apache.hadoop.sso.poc.PocServer"
This needs to be migrated to Knox with a picketlink provider and KnoxSSO service (jersey based?).
The previous POC effort was proving the above flow and trying to minimize the work required on the UI.
The following notes reflect the INITIAL (AND OBSOLETE) POC state for the above flow:
hide footbox autonumber participant "Browser" as cli participant "WebUI\n(eg NN UI)" as ui participant "Knox\nTS/SSO" as sso participant "SAML\nIdP" as idp activate cli cli -> ui: page.GET() activate ui cli <-- ui: redirect(IDP.login) deactivate ui cli -> idp: login.GET() activate idp cli <-- idp: form deactivate idp cli -> idp: form.POST(username,password) activate idp cli <-- idp: redirect(SSO.translate):saml-bearer-token deactivate idp cli -> sso: translate.GET(saml-bearer-token) activate sso cli <-- sso: redirect(WebUI.page):jwt-bearer-token-cookie deactivate sso cli -> ui: page.GET(jwt-bearer-token-cookie) activate ui cli <- ui: response deactivate ui deactivate cli |
hide footbox autonumber participant "Browser" as cli participant "WebUI" as ui participant "SSO" as sso participant "LDAP" as ldap activate cli cli -> ui: page.GET() activate ui cli <-- ui: redirect(SSO.login) deactivate ui cli -> sso: login.GET() activate sso cli <-- sso: challenge deactivate sso cli -> sso: login.GET(credentials) activate sso sso -> ldap: authenticate(credentials) cli <-- sso: redirect(WebUI.page):cookie deactivate sso cli -> ui: page.GET(cookie) activate ui cli <-- ui: response deactivate ui deactivate cli |
hide footbox autonumber participant "Browser" as cli participant "WebUI" as ui participant "SSO" as sso participant "LDAP" as ldap activate cli cli -> ui: page.GET() activate ui cli <-- ui: redirect(SSO.login) deactivate ui cli -> sso: login.GET() activate sso cli <-- sso: form deactivate sso cli -> sso: login.POST(credentials) activate sso sso -> ldap: authenticate(credentials) cli <-- sso: redirect(WebUI.page):cookie deactivate sso cli -> ui: page.GET(cookie) activate ui cli <-- ui: response deactivate ui deactivate cli |