こんにちは、サイオステクノロジー技術部の小山です。
Shibboleth IdP+MFA Serverの後編です。
前回でMFA Server側は設定出来たので、今回はShibboleth IdP側の設定を行います。
Shibboleth IdPに必要な設定
Shibboleth IdPでは、多要素認証の出し分けのため下記2つの設定を行います。
・MFA Server用の認証フロー作成
・認証フローの出し分け(内部はパスワード、外部はパスワード+MFA)
MFA Server用の認証フロー作成
パスワード+MFAの認証を行うため、MFA Server用の認証フローを作成します。
Shibboleth IdPの認証フローは、下記のような設定ファイルで構成されています。
・xxxx-authn-flow.xml
・xxxx-authn-beans.xml
・xxxx-authn-config.xml
今回はMFA利用時の認証先をMFA Serverに切り替えるため、通常のパスワード認証フローを参考にMFA Server用のLDAP認証フローを作成していきます。
・作業対象:Shibboleth IdPサーバー
MFA Server認証フロー用のディレクトリを作成します。
# cd /opt/shibboleth-idp/ # mkdir flows/authn/Password2
パスワード認証フローをコピーします。その後、読み込むべきbeansファイル名を置換します。
# cp system/flows/authn/password-authn-flow.xml flows/authn/Password2/mfas-authn-flow.xml # cp system/flows/authn/password-authn-beans.xml flows/authn/Password2/mfas-authn-beans.xml # sed -i 's/password-authn-beans.xml/mfas-authn-beans.xml/' flows/authn/Password2/mfas-authn-flow.xml
beansファイルがMFA用の設定ファイルを参照するよう置換します。
# sed -i 's/password-authn-config.xml/passwordmfa-authn-config.xml/' flows/authn/Password2/mfas-authn-beans.xml
パスワード認証フロー用の設定ファイルをコピーし、読み込むべき設定ファイル名を置換します。
# cp conf/authn/password-authn-config.xml conf/authn/passwordmfa-authn-config.xml # sed -i 's/ldap-authn-config.xml/ldapmfa-authn-config.xml/' conf/authn/passwordmfa-authn-config.xml
general-authn.xmlにフローを登録します。
# vim conf/authn/general-authn.xml
... <bean id="authn/Password" parent="shibboleth.AuthenticationFlow" p:passiveAuthenticationSupported="true" p:forcedAuthenticationSupported="true" /> + <bean id="authn/Password2" parent="shibboleth.AuthenticationFlow" + p:passiveAuthenticationSupported="true" + p:forcedAuthenticationSupported="true" /> + <bean id="authn/Duo" parent="shibboleth.AuthenticationFlow" p:forcedAuthenticationSupported="true" p:nonBrowserSupported="false"> <!-- The list below should be changed to reflect whatever locally- or community-defined values are appropriate to represent MFA. It is strongly advised that the value not be specific to Duo or any particular technology. -->
MFAServer用の設定パラメータを作成します。
idp.authn.LDAP~のパラメータ名の末尾に、”2″を追加します。
# cp conf/authn/ldap-authn-config.xml conf/authn/ldapmfa-authn-config.xml # sed -i -r 's/(idp.authn.LDAP[^:]+)([:}])/\12\2/' conf/authn/ldapmfa-authn-config.xml
ldap.propertiesに上記で作成したMFAServer用のパラメータを追加します。
LDAPのタイムアウト値は、Azure上で設定した双方向OTP有効時間に合わせ、長めに設定します。
# vim conf/ldap.properties
# LDAP authentication configuration, see authn/ldap-authn-config.xml # Note, this doesn't apply to the use of JAAS +############################################################################################################################# +# GENERAL +############################################################################################################################# + ## Authenticator strategy, either anonSearchAuthenticator, bindSearchAuthenticator, directAuthenticator, adAuthenticator idp.authn.LDAP.authenticator = directAuthenticator ## Connection properties ## idp.authn.LDAP.ldapURL = ldap://ldapserver.example.com:389 idp.authn.LDAP.useStartTLS = false idp.authn.LDAP.useSSL = false # Time in milliseconds that connects will block idp.authn.LDAP.connectTimeout = PT3S # Time in milliseconds to wait for responses idp.authn.LDAP.responseTimeout = PT3S ## SSL configuration, either jvmTrust, certificateTrust, or keyStoreTrust #idp.authn.LDAP.sslConfig = certificateTrust ## If using certificateTrust above, set to the trusted certificate's path idp.authn.LDAP.trustCertificates = %{idp.home}/credentials/ldap-server.crt ## If using keyStoreTrust above, set to the truststore path #idp.authn.LDAP.trustStore = %{idp.home}/credentials/ldap-server.truststore ## Return attributes during authentication idp.authn.LDAP.returnAttributes = * ## DN resolution properties ## # Search DN resolution, used by anonSearchAuthenticator, bindSearchAuthenticator # for AD: CN=Users,DC=example,DC=org idp.authn.LDAP.baseDN = ou=users,dc=example,dc=com idp.authn.LDAP.subtreeSearch = false idp.authn.LDAP.userFilter = (uid={user}) # bind search configuration # for AD: idp.authn.LDAP.bindDN=adminuser@domain.com idp.authn.LDAP.bindDN = cn=manager,dc=example,dc=com idp.authn.LDAP.bindDNCredential = secret # Format DN resolution, used by directAuthenticator, adAuthenticator # for AD use idp.authn.LDAP.dnFormat=%s@domain.com idp.authn.LDAP.dnFormat = uid=%s,ou=users,dc=example,dc=com + + +############################################################################################################################# +# MFA +############################################################################################################################# + +idp.authn.LDAP.authenticator2 = directAuthenticator +idp.authn.LDAP.ldapURL2 = ldap://mfaserver.example.com:389 +idp.authn.LDAP.useStartTLS2 = false +idp.authn.LDAP.useSSL2 = false +idp.authn.LDAP.connectTimeout2 = PT240S +idp.authn.LDAP.responseTimeout2 = PT240S +#idp.authn.LDAP.sslConfig2 = certificateTrust +idp.authn.LDAP.trustCertificates2 = %{idp.home}/credentials/ldap-server.crt +#idp.authn.LDAP.trustStore2 = %{idp.home}/credentials/ldap-server.truststore +idp.authn.LDAP.returnAttributes2 = * +idp.authn.LDAP.baseDN2 = ou=users,dc=example,dc=com +idp.authn.LDAP.subtreeSearch2 = false +idp.authn.LDAP.userFilter2 = (uid={user}) +idp.authn.LDAP.bindDN2 = cn=manager,dc=example,dc=com +idp.authn.LDAP.bindDNCredential2 = secret +idp.authn.LDAP.dnFormat2 = uid=%s,ou=users,dc=example,dc=com + + +############################################################################################################################# +# RESOLVER +############################################################################################################################# # LDAP attribute configuration, see attribute-resolver.xml # Note, this likely won't apply to the use of legacy V2 resolver configurations idp.attribute.resolver.LDAP.ldapURL = %{idp.authn.LDAP.ldapURL} idp.attribute.resolver.LDAP.connectTimeout = %{idp.authn.LDAP.connectTimeout:PT3S} idp.attribute.resolver.LDAP.responseTimeout = %{idp.authn.LDAP.responseTimeout:PT3S} idp.attribute.resolver.LDAP.baseDN = %{idp.authn.LDAP.baseDN:undefined} idp.attribute.resolver.LDAP.bindDN = %{idp.authn.LDAP.bindDN:undefined} idp.attribute.resolver.LDAP.bindDNCredential = %{idp.authn.LDAP.bindDNCredential:undefined} idp.attribute.resolver.LDAP.useStartTLS = %{idp.authn.LDAP.useStartTLS:true} idp.attribute.resolver.LDAP.trustCertificates = %{idp.authn.LDAP.trustCertificates:undefined} idp.attribute.resolver.LDAP.searchFilter = (uid=$resolutionContext.principal) # LDAP pool configuration, used for both authn and DN resolution #idp.pool.LDAP.minSize = 3 #idp.pool.LDAP.maxSize = 10 #idp.pool.LDAP.validateOnCheckout = false #idp.pool.LDAP.validatePeriodically = true #idp.pool.LDAP.validatePeriod = PT5M #idp.pool.LDAP.prunePeriod = PT5M #idp.pool.LDAP.idleTime = PT10M #idp.pool.LDAP.blockWaitTime = PT3S #idp.pool.LDAP.failFastInitialize = false
認証方式出し分けの設定
Shibboleth IdPが内部と外部で認証方式を出し分け出来るように設定します。
・作業対象:Shibboleth IdPサーバー
idp.propertiesで認証方式【MFA】を使用するように変更します。
※ここで指定する【MFA】とは、後述のmfa-authn-config.xmlを使用するための設定です。MFA Serverを指定しているわけではありません。
# vim conf/idp.properties
# Regular expression matching login flows to enable, e.g. IPAddress|Password -idp.authn.flows= Password +idp.authn.flows= MFA
IPアドレスによって、通常の認証フローか、MFA Server用の認証フローか判別する設定を行います。
mfa-authn-config.xmlで以下のように記載します。
# vim conf/authn/mfa-authn-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="https://www.springframework.org/schema/beans" xmlns:context="https://www.springframework.org/schema/context" xmlns:util="https://www.springframework.org/schema/util" xmlns:p="https://www.springframework.org/schema/p" xmlns:c="https://www.springframework.org/schema/c" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd https://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd" default-init-method="initialize" default-destroy-method="destroy"> <!-- This is a map of transition rules that guide the behavior of the MFA flow and controls how factors are sequenced, skipped, etc. The key of each entry is the name of the step/flow out of which control is passing. The starting rule has an empty key. Each entry is a bean inherited from "shibboleth.authn.MFA.Transition". Per the Javadoc for net.shibboleth.idp.authn.MultiFactorAuthenticationTransition: p:nextFlow (String) - A flow to run if the previous step signaled a "proceed" event, for simple transitions. p:nextFlowStrategy (Function<ProfileRequestContext,String>) - A function to run if the previous step signaled a "proceed" event, for dynamic transitions. Returning null ends the MFA process. p:nextFlowStrategyMap (Map<String,Object> where Object is String or Function<ProfileRequestContext,String>) - Fully dynamic way of expressing control paths. Map is keyed by a previously signaled event and the value is a flow to run or a function to return the flow to run. Returning null ends the MFA process. When no rule is provided, there's an implicit "null" that ends the MFA flow with whatever event was last signaled. If the "proceed" event from a step is the final event, then the MFA process attempts to complete itself successfully. --> <util:map id="shibboleth.authn.MFA.TransitionMap"> <!-- First rule runs the IPAddress login flow. --> <entry key=""> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor" /> </entry> <!-- An implicit final rule will return whatever the final flow returns. --> </util:map> <!-- Example script to see if second factor is required. --> <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript" p:customObject-ref="shibboleth.HttpServletRequest"> <constructor-arg> <value> <![CDATA[ logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.idp.attribute"); nextFlow = "authn/Password2"; allowIp = "10.8.20." logger.debug( '[MFA] clientIP: '+ custom.remoteAddr ); // Chack IPAddress if (custom.remoteAddr.startsWith(allowIp)) { nextFlow = "authn/Password"; logger.debug( '[MFA] client IP is internal IP.' ); } else { logger.debug( '[MFA] client IP is external IP.' ); } logger.debug( '[MFA] Next Flow is ' + nextFlow ); nextFlow; // pass control to second factor or end with the first ]]> </value> </constructor-arg> </bean> </beans>
設定が終わったらShibboleth IdPを再起動してください。
これで、Shibboleth IdPの設定も完了しました。
動作確認
最後に、内部ネットワークと外部ネットワークからそれぞれ動作確認を行います。
・作業対象:クライアントPC
内部ネットワークから
内部ネットワークからSPにアクセスし、Shibboleth IdPのログイン画面にリダイレクトされます。
ユーザー名とパスワードを入力して、loginボタンをクリックします。
パスワード認証だけで認証成功します。
Shibboleth IdPのデバッグログを見ると、authn/Password(パスワード認証)が選択されていました。
2018-04-26 02:19:34,864 - DEBUG [net.shibboleth.idp.attribute:5] - [MFA] clientIP: 10.8.20.6 2018-04-26 02:19:34,917 - DEBUG [net.shibboleth.idp.attribute:10] - [MFA] client IP is internal IP. 2018-04-26 02:19:34,927 - DEBUG [net.shibboleth.idp.attribute:13] - [MFA] Next Flow is authn/Password
外部ネットワークから
外部ネットワークからSPにアクセスし、Shibboleth IdPのログイン画面にリダイレクトされます。
ユーザー名とパスワードを入力して、loginボタンをクリックします。
そのまま認証成功とはならず、SMSでOTPを受信します。
そのままOTP入力して返信します。
OTP返信後、Shibboleth IdPの認証画面から遷移し、認証に成功します。
Shibboleth IdPのデバッグログを見ると、authn/Password2(パスワード認証+多要素認証)が選択されていることがわかります。
2018-04-26 02:35:36,551 - DEBUG [net.shibboleth.idp.attribute:5] - [MFA] clientIP: xx.xx.xx.xx(<-外部のIP) 2018-04-26 02:35:36,632 - DEBUG [net.shibboleth.idp.attribute:12] - [MFA] client IP is external IP. 2018-04-26 02:35:36,635 - DEBUG [net.shibboleth.idp.attribute:15] - [MFA] Next Flow is authn/Password2(<-パスワード認証+多要素認証のフロー)
と、いうことで期待通りの動作となりました!
おわりに
MFA ServerとShibboleth IdPを使った多要素認証環境を構築することができました。
実際に動かして分かりましたが、Shibboleth IdPのログイン画面でログイン中、OTPを返信するまで、(OTP返信を待っているため)画面が応答しなくなっているように見えてしまいました。
↑この画面のログイン処理が応答しなくなっているかのような挙動になります。
ログイン画面に注釈を入れるなど、Shibboleth IdP側はもう少し考慮が必要そうです。
以上です。