はじめに
リンカーズ株式会社エンジニアの鈴木です。この記事では RubySAML Gem を利用したSAML SSO認証機能について解説していきます。
SAMLは巨大な仕様であるため、解説すべき事項もかなり多いものとなります。このブログでは解説事項を「IdP関係」「SP関係」「セキュリティ関係」の3種に分け(「IdP」「SP」が何なのかについては後述)、この記事では主に「IdP」に関する項目を解説します。
SAMLとは
SAML (Security Assertion Markup Language) とは、異なるドメイン間で認証・認可に関するデータを交換するためのプロトコルです。ユーザの認証情報や属性、ユーザへの権限の認可といったセキュリティ情報がXML形式で送受信されます。
SAMLを利用することでシングルサインオン (SSO) ならびにシングルログアウト (SLO) が実現できます。詳しくは以下の記事などを参照ください。
- シングル サインオンの SAML プロトコル - Microsoft identity platform | Microsoft Learn
- Azure シングル サインアウトの SAML プロトコル - Microsoft identity platform | Microsoft Learn
SAMLを理解する上で重要な概念である Identity Provider (IdP), Service Provider (SP), Name ID および SAML Metadata を次で説明します。
Identity Provider (IdP)
利用者に対して認証情報を提供するシステムです。具体例としては以下のサービスが知られています。
Service Provider (SP)
IdPが提供する認証情報を利用して、エンドユーザーに機能を提供するシステムです。
Name ID
IdP - SP間共通で認証対象のユーザーを一意に特定する情報を Name ID といい、SAMLが扱う情報の中で最も重要なものです。SAMLとはこのName IDをセキュアにやり取りすることが最大の目的であると言えます。Name IDという名前は当ブログでもしばしば登場します。
Metadata
SPまたはIdPの情報を記述したXMLファイルです。SPにはSP記述用の、IdPにはIdP記述用の仕様が存在します。内容としては以下の情報が主に含まれます。
- エンティティID
- ドメイン名やハッシュ値などを結合し、アプリケーションごとに一意になるよう定められた文字列
- サーバー証明書
- PEM形式 文字列
- エンドポイントURI
- 各アプリケーションが定義している、SAML関係のリクエストを受け付けるURL
多くのIdPがMetadataの公開URLまたはMetadata XMLファイルのダウンロード機能を提供しています。自分のシステムを改修してSAML SSOに対応させる場合、そのシステムにはIdP Metadata (URLまたはファイル) の読み込み機能も提供した方がエンドユーザーにとって便利です。
RubySAML Gemについて
RubySAMLは、SAML認証を実装しようとするクライアント (SP) のためのライブラリで、IdPとのリクエストに用いるXMLの構築・パース機能やサーバー証明書の認証機能を提供しています。
GitHub - SAML-Toolkits/ruby-saml: SAML SSO for Ruby
OneLogin::RubySaml::Settings
クラス
運用中のプロダクトにRubySAMLを導入してSAMLを実現する場合、設定項目を正しく決める必要があります。SAML設定項目は OneLogin::RubySaml::Settings
クラスにまとめられています。
このシリーズでは OneLogin::RubySaml::Settings
の各項目の解説と、このGemを利用する上で設定値をどう決めるべきか解説します。
ソースコード のコメントで言及されている通り、SAML設定項目は大きく分けて「IdP関係」「SP関係」「セキュリティ」の3種類に分類されます。この記事では IdP関係 の設定項目について説明します。SP関係・セキュリティ関係はまた記事を改めて紹介する予定です。
IdP関係オプションの解説
プロダクトが接続する対象のIdPに関する設定を保存します。外部のサービスに関するパラメータであるため、システムとして提供する際はDBに設定値保存用のテーブルを用意した上で、設定値編集用のUIも提供すべきでしょう。
idp_entity_id
IdPが自身を一意に特定するためのIDです。IdPが公開しているため、それを設定します。
idp_sso_service_url
シングルサインオンのリクエストを受け付けるURLです。これもIdPが公開しています。
idp_sso_service_binding
シングルサインオンのリクエストを実行する際の通信プロトコルです。以下の文字列のうちどちらかを設定します。
urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
(以下、単にPOST
)urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
(以下Redirect
)
Ruby on Railsアプリケーションの場合、この値は Redirect
に固定 することで充分運用できます。理由はのちほど詳しく解説します。
idp_slo_service_url
シングルログアウトのリクエストを受け付けるURLです。IdPが公開しているURLを設定します。
idp_slo_service_binding
シングルログアウトのリクエストを実行する際の通信プロトコルです。 idp_sso_service_binding
と同じ選択肢 (POST
, Redirect
) になります。
idp_slo_response_service_url
シングルログアウト機能に関するURLです。これもIdPが公開しているURLを設定します。
idp_slo_service_url
との違いは、あとで詳しく解説します。
idp_cert
X.509公開鍵暗号体系 におけるIdPのサーバー証明書 (CRT) です。各種URLと同様、IdPが公開しています。
記述方式は PEM形式 の文字列なので、XMLファイルに直接埋め込むことができます。
idp_cert_fingerprint
IdPサーバー証明書の フィンガープリント です。
idp_cert_fingerprint_algorithm
フィンガープリント計算に使われたアルゴリズムです。SHA1, SHA256, ... などが設定されます。
idp_cert_multi
IdPサーバー証明書を複数保存するための設定です。これに対応することで、有効期限が近づいたサーバー証明書の更新の際、新・旧両者の証明書を登録することでダウンタイムなく更新を実施することが可能です。SAML設定機能を提供するのであれば、IdPサーバー証明書の複数登録には対応しておくことを強くお勧めします。
中身は以下の構造のHashです。キーである :signing
と :encryption
は固定で、Valueとして証明書のPEM形式文字列のArrayが入ります。
settings.idp_cert_multi # => { :signing => ["MII...", "MII..."], # :encryption => ["MII...", "MII..."] }
idp_cert
(および idp_cert_fingerprint
, idp_cert_fingerprint_algorithm
) と idp_cert_multi
のどちらを利用すべきか、また :signing
と :encryption
の詳細については後述します。
idp_attribute_names
IdPが扱うユーザー情報の属性のうち、対応している属性情報の一覧をmetadataに含めることができ、これを扱うための項目です。
これに関しては設定によりRubySAMLの挙動に影響することはないので、運用上は無視しても問題ありません。
idp_name_qualifier
Name ID に追加的に意味を持たせたい場合に設定されますが、基本的に無くても利用できます。 公式仕様書 にも「IDの用途と意味を明確に決めていない場合は省略すべき」と言及されています。
The
NameQualifier
andSPNameQualifier
attributes SHOULD be omitted unless the identifier's type definition explicitly defines their use and semantics.
valid_until
IdP metadata自体の有効期限がmetadataに含まれているので、これを読み取るための項目です。SAMLの挙動には影響しないため、無視しても問題ありません。
自前でmetadataの読み込み機能を提供する場合、この valid_until
と現在時刻を比較し、現在時刻の方が超えていたら読み込み処理をエラーにするという用途に使える可能性があります。
補足
Bindingの選択: POST
or Redirect
?
Bindingは Redirect
で充分であるとした理由について、次の2つの判断基準を説明します。
- IdPが対応していること
- Railsとしての実装上の都合
IdPが対応していること
まず、この値はIdPから idp_sso_service_url
と一体的に提供されます。具体的にはIdP Metadataに以下の通り記載されており、 Location
属性が idp_sso_service_url
, Binding
属性が idp_sso_service_binding
に設定すべき値となります。
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2" /> <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2" />
IdPによってはどちらかのBindingしか対応していなかったり、またBindingによってLocationが異なるケースも存在するようです。いずれにせよ、IdP Metadataを確認すれば idp_sso_service_url
に対応する idp_sso_service_binding
は読み取れます。
上記の場合、このIdPは同じURL (Location) で POST
, Redirect
どちらのBindingも対応していることになります。
Railsとしての実装上の都合
Railsアプリに関しては、多くの場合 Redirect
の方が圧倒的に都合がいいはずです。
GitHubで公開されている ruby-saml-example を例に取って説明すると、ユーザーからの操作を受け取って上記 idp_sso_service_url
を利用する部分は以下の実装になっています。なお、この sso
アクションは GET
メソッドです。
def sso settings = Account.get_saml_settings(get_url_base) if settings.nil? render :action => :no_settings return end request = OneLogin::RubySaml::Authrequest.new redirect_to(request.create(settings)) end
ここで分かる通り, Controllerの実装中に OneLogin::RubySaml::Authrequest#create
の実行結果 (idp_sso_service_url
にパラメータを追加したString) に対して redirect_to
しています。もしここでPOSTリクエストを送信するのであれば idp_sso_service_binding
は POST
であるべきでしょう。
ただ、Railsに関してはController内から外部のWebサイトにPOSTリクエストを送信する実装は redirect_to
に比べてコストが高くなります。このため、Railsアプリの実装では redirect_to
を使い、またこの事情のために idp_sso_service_binding
も Redirect
を使う方針が便利でしょう。
idp_slo_service_url
と idp_slo_response_service_url
の違い
シングルログアウト (SLO) とは、同一のIdPを使って複数のSPにログインしている場合に、1回の操作で全てのSPからログアウトする機能です。 idp_slo_service_url
と idp_slo_response_service_url
はどちらもこのSLOに関して使われるURLで、このSLOの説明を通じて両者の違いを解説します。
SLOの処理フローについて、以下の図を用いて解説します。
出典: https://www.identityserver.com/articles/the-challenge-of-building-saml-single-logout
この図のケースでは、ユーザーは単一のIdPを使って3つのサービス「SP 1」「SP 2」「SP 3」にログインしています。ここで「SP 1」上でシングルログアウトを実行すると、ログイン中の「SP 2」および「SP 3」からもログアウトされるという機能です。
最初に起点となる、SP (上記例での「SP 1」) からIdPへシングルログアウトをリクエストするURLが idp_slo_service_url
です。そしてこの機能を実現するために、他のSP (上記例での「SP 2」「SP 3」) はIdPからログアウト要求を受け付け、これに応答してIdPにリクエストを送り返す処理フローが存在します。この時のIdPに送り返すURLが idp_slo_response_service_url
です。
シングルログアウトの処理フローと、各種URLとの関係をまとめると以下の通りです。
- ユーザーがSP 1のシングルログアウトを起動
- SP 1はIdPに対してシングルログアウトリクエストを送信 →
idp_slo_service_url
- IdPはSP 2, SP 3に対してログアウトリクエストを送信 →
single_logout_service_url
(SP設定項目。別の記事で解説) - SP 2, SP 3はログアウト処理を実行し、IdPにリクエストを送信 →
idp_slo_response_service_url
証明書の保存先: idp_cert
or idp_cert_multi
RubySAMLでは、同じIdPサーバー証明書を表現するために idp_cert
と idp_cert_multi
2つの項目を定義しています。これについては、基本的に idp_cert_multi
のみ を利用していれば問題はありません。
idp_cert_multi
が設定された場合は idp_cert
より優先されて利用されます (README より) 。またIdP metadataの読み込み処理において、サーバーが提供する証明書が1個だけでも idp_cert_multi
は必ず設定されます (PR #611) 。このため idp_cert_multi
だけ設定しておけばSAML SSOの運用が可能です。
Fingerprint は無くていいのか
ただし、 idp_cert
には対応して idp_cert_fingerprint
・ idp_cert_fingerprint_algorithm
が定義されている一方、 idp_cert_multi
にはFingerprintを保存する機能がありません。この点については、以下の理由から運用上の問題はありません。
まず前提として、このIdPサーバー証明書はIdPから送信されたレスポンスをSP側で検証するために利用されます。そしてIdPサーバーからのレスポンスには 証明書そのもの (<X509Certificate>
タグ) が必ず含まれます。
RubySAMLは「事前に登録したIdPサーバー証明書」と「レスポンスに含まれるIdPサーバー証明書」を比較することでレスポンスの妥当性を保証します。この比較処理で idp_cert
(fingerprintあり) と idp_cert_multi
(fingerprintなし) で異なる処理を実行しています。
- fingerprintあり: レスポンスに含まれるIdP証明書と
idp_cert_fingerprint_algorithm
からfingerprintを計算し、これとidp_cert_fingerprint
を比較 - fingerprintなし: レスポンスに含まれるIdP証明書の
to_pem
とidp_cert_multi
のto_pem
を比較
(出典: ruby-saml/lib/xml_security.rb at master · SAML-Toolkits/ruby-saml)
つまり「fingerprintがあればそれを比較し、無ければPEM形式文字列を比較する」というコンセプトを取っています。このため、証明書によるサーバー認証処理ではfingerprintは必須とされていません。
証明書の :signing
と :encryption
について
先述の通り、 idp_cert_multi
は :signing
と :encryption
をキーとするハッシュを設定します。それぞれに設定すべき値の方針について、結論から述べると :signing
のみ 設定し、 :enctyption
については無視 して構いません。
:signing
は実際にIdPからのリクエストの検証に参照されるため設定が必須です。一方 :encryption
証明書はRubySAMLのどの処理からも参照されず、システムの挙動には影響しないためです。
:encryption
が存在する理由
:encryption
という名前から、この証明書はSAMLにおける暗号化機能 (次で解説) で使われていそうな感じですが、実際には使われいません。
では、なぜ :encryption
設定が存在するかというと、これはSAML Metadataの証明書関係の仕様、および暗号化機能の性質が影響していると考えられます。
SAML Metadataには証明書関係の仕様も含まれます。SP MetadataとIdP Metadataはそれぞれ独立した仕様ではありますが、記述がSPとIdPで対称的になっている部分もあります。
SPの証明書は署名と暗号化の両方に使われるため、SP Metadataについても署名 (sign
属性) と暗号化 (encrypt
属性) それぞれの機能に対応した仕様が定義されています。
The providers MAY document the key(s) used to sign requests, responses, and assertions with
<md:KeyDescriptor>
elements with a use attribute ofsign
. When encrypting SAML elements,<md:KeyDescriptor>
elements with a use attribute ofencrypt
MAY be used to document supported encryption algorithms and settings, and public keys used to receive bulk encryption keys.
https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf
そして、これと同じことがIdP Metadataの仕様書にも記載されています。つまりIdP metadataの証明書セクション (<md:KeyDescriptor>
タグ) にも sign
と encrypt
どちらの属性も記述 "できる" ことになっています。
ただし、後述の通りIdP Metadataが利用される場面では セキュアな情報を扱っていない ため、暗号化機能が提供されていません。このためIdPの encrypt
証明書は "定義できるが実質的には使われない" という状態です。
SAMLにおける暗号化とは
SAMLには暗号化機能が定義されており、これを有効にすると XMLENC という仕様に基づいてIdPからSPへのレスポンスXMLが暗号化されます (参考: Microsoftによる解説) 。これを利用することで、レスポンスに含まれる 認証済ユーザーの Name ID というSAMLにおける最もセキュアな情報を暗号化により保護することができます。
RubySAMLも暗号化機能に対応しています (RubySAML: Decrypting IdP SAML assertions) 。利用方法を簡単に説明します。
事前準備
- SPに復号用の秘密鍵を登録
- SPとIdP両者に、秘密鍵に対応する公開鍵が含まれる証明書を登録
- IdPに「このSPへのレスポンスを暗号化する」フラグを設定する
認証実行時
- SPからIdP: 認証リクエストを送信
- IdP: 登録した証明書に含まれる公開鍵でレスポンスを暗号化
- IdPからSP: 暗号化したレスポンスを送信
- SP: 登録した秘密鍵でレスポンスを復号
これを踏まえると、反対方向の通信 - つまり「SPからIdPへの通信を暗号化したい」場合は、SP側にIdPの提供した「暗号化用IdP証明書」を設定する必要が生じます。
- IdPに復号用の秘密鍵を登録
- SPとIdP両者に、秘密鍵に対応する公開鍵が含まれる証明書 (「暗号化用IdP証明書」) を登録
- 効果: SPからIdPへのリクエストが暗号化できる
しかし、この機能は提供されていません。理由としては 情報の重要度が異なるため で、SPからIdPへのリクエストにはName IDに匹敵するセキュアな情報は含まれません。
結論として、このケースに対応する暗号化機能がそもそも提供されておらず、したがって「暗号化用IdP証明書」つまり idp_cert_multi
の :encryption
に値の設定は不要であるとの結論が得られます。
おわりに
以上、 OneLogin::RubySaml::Settings
の項目のうち、IdPに関する項目の解説と、おすすめする設定値の見解でした。
冒頭で述べた通り、この記事では設定項目の1/3ほどしか解説できていません。「SP編」「セキュリティ編」の記事も計画中なので、しばしお待ちを…。