本連載記事は、主にアプリケーション開発者を対象の読者とし、ネットワーク上の新たな境界として台頭しつつある「アイデンティティ型の境界」を実現するための数ある認証連携方式の中から、「OpenID Connect」に注目して仕様説明と有用性を解説します。
最終回の今回は、OpenID Connectで連携されたアイデンティティ情報を活用したOpenAMとAWSマネジメントコンソールの認証統合の実装をご紹介します。AWSマネジメントコンソールはAWS上の様々なリソースを操作するGUIとなります。詳細はAWSマネジメントコンソールのページをご覧ください。
AWS IAMを使った認証と認可
AWS上でアプリケーション開発や運用をする場合、AWSの利用者はAWSマネジメントコンソールにアクセスし、EC2インスタンスの作成や停止、再起動などのリソースの操作を行います。これらの操作を行なう場合、AWS Identity and Access Management (IAM)で管理されているユーザーアカウント(IAM User)のアイデンティティ情報を用いて認証をする必要があります。詳細はAWS IAMのページをご覧ください。
また、AWSのリソースの操作の認可はIAM Policyという特定の書式に従って記述されたルールによって制御されます。この認可制御は、AWS IAM上で一元的に管理されており、IAM User、もしくは、IAM Userを束ねたグループに対し、適用することができます。よって、AWSマネジメントコンソールの認証と認可はAWS IAMを適切に運用し、監視することで実現可能です。
AWS IAMの運用が形骸化していないですか?
しかし、AWS上でアプリケーション開発や運用をする際、会社や部門、チームなどの組織で業務に臨むことがほとんどでしょう。すると、認証に用いるユーザーのアイデンティティの所在はAWS IAMではなく、組織内(あるいは、閉じたネットワーク内)のリポジトリとなります。それゆえ、組織内のリポジトリ上のアイデンティティ管理とAWS IAM上のアイデンティティ管理を二重で運用する必要があり、運用の期間を経るにつれて、アイデンティティ管理の運用プロセスが形骸化する可能性が出てきます。また、アイデンティティ情報に紐付いた認可制御が設定されなくなるおそれがあり、これらのリスクは、結果的に以下に挙げるような重大な問題として、顕在化する可能性があります。
- 退職したユーザーがAWS上の資産を持ち出すことがある
- 入社したユーザーがAWS上の資産にアクセスできない
- 職務分掌にそぐわないAWS上の資産へのアクセスがある
上述の問題の対処をAWS IAMだけで実装することは難しいのが現状です(2016年4月現在)。しかし、アイデンティティ情報の連携技術を用いることにより、組織内のアイデンティティ情報を組織の境界を超えて、AWSに連携し、2つのプロバイダ間のアイデンティティ管理を一元化することが可能です。次章より、それらの実装の一例をご紹介します。
組織の認証基盤にサインインしているユーザーにAWSマネジメントコンソールへのアクセスを許可する
前章に挙げた、問題の対処のために、AWSは以下の実装例を紹介しています。
この実装は、組織の認証基盤にサインインされていることを確認し、その情報を信頼して、AWSマネジメントコンソールのサインインURLを作成する実装例となっています。 今回はこの実装例に倣って、OpenID Connectを用いた、AWSマネジメントコンソールへのシングルサインオンと役割に応じた権限の配布を実装します。
今回の実装における用語解説
本実装例では以下の用語を用いて解説をします。
用語 | 説明 |
---|---|
組織の認証基盤 | 組織内、あるいは閉じられたネットワーク内で、組織に属するユーザーの認証を行なう基盤である。OpenID Connectでアイデンティティ情報を連携するOpenID Provider(以下、OP)の機能が実装されていることを前提としており、本記事ではOpenAMを使用している。 |
フェデレーションブローカー | 組織の認証基盤から連携されたアイデンティティ情報をもとに、ユーザーが所属している部門に許可された権限が適用された一時ユーザーの作成を行なう。また、その一時ユーザーがAWSマネジメントコンソールにサインインすることができるURLの生成を行なう。アイデンティティ情報の連携を組織の認証基盤から受けるために、Relying Party(以下、RP)の機能が実装されていることを前提としており、本記事ではpassport.jsを組み込んだexpress.jsを使用している。 |
AWS Security Token Service (STS) | AWS上のリソースへのアクセスする権限を持った一時ユーザーを作成することができるAWSのサービス。 |
アクセスの流れ
最初に、実装完了後のアクセスの流れについて解説します。
- ユーザーはフェデレーションブローカー上のサインインのエンドポイントにアクセスします
- フェデレーションブローカー上のリンクから、リダイレクトで組織の認証基盤に認可リクエストを送信します
- ユーザーは組織の認証基盤でユーザー認証します
- 組織の認証基盤は組織のユーザーリポジトリで認証し、認証成功時にユーザーのアイデンティティ情報を取得します
- 組織の認証基盤は認可コードをリダイレクトでフェデレーションブローカーのコールバックのエンドポイントに送信します
- フェデレーションブローカーは認可コードを用いて、組織の認証基盤からアイデンティティ情報の連携を受けます
- フェデレーションブローカーは「6.」で連携されたアイデンティティ情報を用いて、部門番号を識別し、適用できる権限の一覧を作成します
- フェデレーションブローカーは適用できる権限の一覧をユーザーに提示し、ユーザーは必要な権限を要求します
- フェデレーションブローカーは選択された権限を持った一時ユーザーを識別するクレデンシャルをAWS STSの関数を使って生成します
- フェデレーションブローカーは生成したクレデンシャルを用いて、AWSマネジメントコンソールのサインインURLを生成します
- ユーザー側のブラウザにAWSマネジメントコンソールのサインインURLがリダイレクトで送信し、ユーザーのAWSマネジメントコンソールのサインインが完了します
後述の「ID連携中継機能の実装の前に」に挙げる実装や設定が完了している場合、上記のアクセスの流れにおける「1.」~「6.」までの実装(組織の認証基盤で認証後、フェデレーションブローカーにアイデンティティ情報を連携する)が完了していることとなります。 よって、本記事では「7.」~「11.」を実現する追加機能(フェデレーションブローカー内のID連携中継機能)の実装を行います。
ID連携中継機能の実装の前に
フェデレーションブローカー内のID連携中継機能の実装をするにあたり、以下の実装や設定が完了している必要があります。
- 組織の認証基盤のID連携機能(OP)、及び、認証機能の実装
- フェデレーションブローカーのID連携機能(RP)の実装
- 組織のユーザーリポジトリ上にユーザーを作成
- 一時ユーザーに適用する権限を持ったIAM Roleの作成
- 組織の認証基盤から連携されるアイデンティティ情報に部門番号を追加するための設定
組織の認証基盤のID連携機能(OP)、及び、認証機能の実装
組織の認証基盤はOPとしての機能が実装されている必要があります。本記事では、OpenAMを活用し、以下の設定値でOPを実装しています。 OPの詳細な実装方法については、本連載の第二回 OpenID Providerの実装例をご参照ください。
属性名 | 説明 | 本記事での設定値 |
---|---|---|
ホスト名 | OPのFQDN | openidprovider.sample.domain |
issuer identifier | ID TokenのクレームをセットするIssuerの識別子 | https://openidprovider.sample.domain/openam/oauth2 |
authorizationURL | 認証を行うエンドポイント | https://openidprovider.sample.domain:443/openam/oauth2/authorize |
tokenURL | トークンを発行して受け渡すエンドポイント | https://openidprovider.sample.domain:443/openam/oauth2/access_token |
userInfoURL | ユーザーの情報を返すエンドポイント | https://openidprovider.sample.domain:443/openam/oauth2/userinfo |
フェデレーションブローカーのID連携機能(RP)の実装
フェデレーションブローカーはRPとしての機能が実装されている必要があります。本記事では、passport.jsを組み込んだexpress.jsを活用し、以下の設定値でRPを実装しています。 RPの詳細な実装方法については、本連載の第三回 Relying Partyの実装例をご参照ください。
属性名 | 説明 | 本記事での設定値 |
---|---|---|
ホスト名 | RPのFQDN | relyingparty.sample.domain |
client_id | クライアント識別子 | sampleRP |
client_secret | クライアント識別のためのシークレット | secret |
redirect_uri | OPでの認証後にコールバックを受けるエンドポイント | https://relyingparty.sample.domain/cb |
組織のユーザーリポジトリ上にユーザーを作成
本実装例では、以下の部門に属する2つのアカウントを用いた権限の制御を行います。部門番号が000000
という組織に属しているユーザー(以下例のyamada1003
)はAWS上の管理権限が付与されます。
- 部門番号
部門番号 | 部門名 | 備考 |
---|---|---|
000000 | 管理部門 | 管理権限が付与される部門 |
999999 | 利用部門 | 参照権限が付与される部門 |
- アカウント
yamada1003
属性名 | 説明 | 本記事の設定値 | 備考 |
---|---|---|---|
uid | サインインID | yamada1003 | |
departmentNumber | 部門番号 | 000000 | 管理権限が付与される部門番号 |
sn | 姓 | Yamada | |
cn | フルネーム | Yamada1003 | |
userPassword | パスワード | 適宜設定すること | |
inetUserStatus | ユーザー状態 | Active |
- アカウント
sato2007
属性名 | 説明 | 本記事の設定値 | 備考 |
---|---|---|---|
uid | サインインID | sato2007 | |
departmentNumber | 部門番号 | 999999 | 参照権限が付与される部門番号 |
sn | 姓 | Sato | |
cn | フルネーム | Sato2007 | |
userPassword | パスワード | 適宜設定すること | |
inetUserStatus | ユーザー状態 | Active |
一時ユーザーに適用する権限を持ったIAM Roleの作成
フェデレーションブローカーが「アクセスの流れ」の「9.」にて生成する一時ユーザーは、AWS上でリソースの操作を行なう権限を持つ必要があります。よって、この一時ユーザーに充てがう権限を有する以下のIAM Roleを作成し、フェデレーションブローカーが稼働するEC2に設定します。
本実装例では、Roleの名前をIAMRoleForEC2
としています。Arn
プロパティ内のAWSアカウントID
には適宜、ご利用のAWSアカウントIDを入力してください。
属性名 | 本記事での設定値 |
---|---|
ロール名 | IAMRoleForEC2 |
ロールARN(Amazon Resource Name) | arn:aws:iam::AWSアカウントID:role/IAMRoleForEC2 |
信頼されたエンティティ | ID プロバイダー ec2.amazonaws.com |
ポリシー | arn:aws:iam::aws:policy/AmazonEC2FullAccess |
組織の認証基盤から連携されるアイデンティティ情報に部門番号を追加するための設定
組織の認証基盤(OpenAM)は、認可リクエストのscope
パラメータにprofile
を指定しただけでは、部門番号(departmentNumber
)をアイデンティティ情報(claim
)に含まない設定となっています。以下の設定変更を行うことで、departmentNumber
をアイデンティティ情報に含むことができます。
- OIDC Claims Scriptの編集
- OpenID Providerの編集
- Embedded User Data Storeの編集
OIDC Claims Scriptの編集
- 組織の認証基盤(OpenAM)にamadminユーザーでログインします
Top Level Realm
>Scripts
>OIDC Claims Script
を順にクリックし、OIDC Claims Scriptの編集画面に遷移しますScript
を以下のように編集します
// [ {claim}: {attribute retriever}, ... ] claimAttributes = [ // departmentNumberをclaimに追加 "departmentNumber": attributeRetriever.curry("departmentNumber"), "email": attributeRetriever.curry("mail"), "address": { claim, identity, requested -> [ "formatted" : attributeRetriever("postaladdress", claim, identity, requested) ] }, "phone_number": attributeRetriever.curry("telephonenumber"), "given_name": attributeRetriever.curry("givenname"), "zoneinfo": attributeRetriever.curry("preferredtimezone"), "family_name": attributeRetriever.curry("sn"), "locale": attributeRetriever.curry("preferredlocale"), "name": attributeRetriever.curry("cn") ] // {scope}: [ {claim}, ... ] scopeClaimsMap = [ "email": [ "email" ], "address": [ "address" ], "phone": [ "phone_number" ], // departmentNumberをprofileのscopeに追加 "profile": [ "departmentNumber", "given_name", "zoneinfo", "family_name", "locale", "name" ] ]
OpenID Providerの編集
Top Level Realm
>Services
>OAuth2 Provider
を順にクリックし、OpenID Providerの編集画面に遷移しますサポートされているクレーム
にdepartmentNumber|department number
を追加します
Embedded User Data Storeの編集
Top Level Realm
>Data Stores
>embedded
を順にクリックし、Embedded User Data Storeの編集画面に遷移しますLDAP ユーザー属性
にdepartmentNumber
を追加します
ID連携中継機能の実装
それでは、先に示した「アクセスの流れ」の「7.」~「11.」に当たる実装を順番に解説します。
まず、「7.」~「8.」の実装として、index.jsの/cb
エンドポイントに実装を追加します。
追加した実装では、組織の認証基盤のID連携機能(OP)から連携されたアイデンティティ情報を用いて、付与可能な権限のリンクを表示します。ここでは、アイデンティティ情報(claim
)に含まれるdepartmentNumber
を判定に使用しています。権限の強い管理部門(departmentNumber
が000000
)であった場合は、付与される権限を2種類(EC2のFull Access権限とEC2のDescribe権限)表示し、利用部門(departmentNumber
が000000
以外)であった場合は、付与される権限を1種類(EC2のDescribe権限)のみ表示します。ユーザーは権限を選択することで「8.」以降の処理が継続できます。
router.get('/cb', passport.authenticate('openidconnect', {failureRedirect: '/'}), function(req, res) { res.cookie('express', { authInfo: req.authInfo, userInfo: req.user._json }, { secure: true, httpOnly: true } ); var departmentNumber = req.user._json.departmentNumber; res.render( "choose", { // isAdmin()は引数が管理部門の部門番号(000000)であることを確認する admin: isAdmin(departmentNumber) } ); } );
続いて、「9.」~「11.」の実装として、index.jsに/ec2/:id
エンドポイントを追加します。
このエンドポイントは、「8.」 でユーザーが選択した権限によって、:id
に挿入される文字列が変わります。
router.post('/ec2/:id', function(req, res, next) { // 後述のため省略 });
このエンドポイントでは大きく以下の3つのことを実行しています。
- 「8.」で選択された権限に応じたポリシーの適用
- ポリシーが適用された一時ユーザーを識別するクレデンシャルを生成
- クレデンシャルを用い、AWSマネジメントコンソールのサインインURLを生成し、応答する
以下で、それぞれ説明します。
1. 「8.」で選択された権限に応じたポリシーの適用
ユーザーによって権限が選択された場合、/ec2/:id
エンドポイントには/ec2/describe
、もしくは、/ec2/fullaccess
のリクエストが送信されます。ここでは、それらのリクエストに応じて、ユーザーに付与する権限を切り替えています。
// サインイン後に引き受ける権限の基本設定 var params = { RoleArn: 'arn:aws:iam::' + AWS_ACCOUNT + ':role/IAMRoleForEC2', DurationSeconds: 900, }; // 選択された権限に応じたポリシーをパラメータに設定する if (admin && policyRequest == "fullaccess") { //管理部門且つFull Access権限を要求した場合 params.RoleSessionName = "EC2-FULLACCESS-POLICY"; params.Policy = EC2_FULLACCESS_POLICY; } else { //それ以外のユーザーの場合 params.RoleSessionName = "EC2-DESCRIBE-POLICY"; params.Policy = EC2_DESCRIBE_POLICY; }
また、上記実装内のEC2_FULLACCESS_POLICY
とEC2_DESCRIBE_POLICY
はユーザーに割り当てる権限の情報をIAM Policyの記法の形式で定義します。
管理部門の場合は、EC2に関連する全ての権限を許可しています。
{ "Version":"2012-10-17", "Statement":[ { "Action":"ec2:*", "Effect":"Allow", "Resource":"*" } ] }
利用部門の場合は、EC2のインスタンス情報の参照権限のみを許可しています。
{ "Version":"2012-10-17", "Statement":[ { "Action":"ec2:DescribeInstances", "Effect":"Allow", "Resource":"*" } ] }
2. ポリシーが適用された一時ユーザーを識別するクレデンシャルを生成
設定した権限のパラメータを引数に、権限を引き受けた一時ユーザーのクレデンシャルを発行する関数sts.assumeRole()
を実行します。リクエストが成功した場合、権限が適用された900秒間有効な一時ユーザーのセッションに紐付いたクレデンシャルが返却されます。これらのクレデンシャルを用い、AWSマネジメントコンソールのサインインURLを作成することができます。
// 選択された権限でサインインが可能なURLを生成する sts.assumeRole(params, function(err, data) { if (err) { // Error処理を記述 } else { // サインインURL取得に必要な要求を作成する var sessionJson = { "sessionId": data.Credentials.AccessKeyId, "sessionKey": data.Credentials.SecretAccessKey, "sessionToken": data.Credentials.SessionToken }; var session = encodeURIComponent(JSON.stringify(sessionJson)) var signinParam = "?Action=getSigninToken" + "&SessionType=json" + "&Session="+session; var options = { url: SIGNIN_URL + signinParam, json: true };
3. クレデンシャルを用い、AWSマネジメントコンソールのサインインURLを生成し、応答する
AWSが用意しているフェデレーションエンドポイント(CONSOLE_URL
)に対し、sts.assumeRole()
関数で生成したクレデンシャルをQueryStringに含めて送信することで、選択した権限が適用された状態でサインインが完了します。
// サインインURLの作成し、リダイレクト応答をする request.get(options, function (error, response, body) { var signinToken = body.SigninToken; var issuer = iss; var redirectQuery = "?Action=login" + "&SigninToken="+encodeURIComponent(signinToken) + "&Issuer="+encodeURIComponent(iss) + "&Destination="+encodeURIComponent(CONSOLE_URL); var redirect = SIGNIN_URL + redirectQuery res.redirect(redirect); });
動作確認
ここまでの実装が完了した場合、以下のような動作を確認することができます。
管理部門に属するユーザーyamada1003
でサインインした場合
yamada1003
で組織の認証基盤にサインインし、フェデレーションブローカーにアイデンティティ情報の連携をした場合、EC2のFull Access権限とEC2のDescribe権限の双方が要求可能となっています。ここでは、EC2のFull Access権限を要求しており、AWSマネジメントコンソールにシングルサインオンが完了すると、EC2関連の全てのリソース状況が表示される状態となっています。
下はyamada1003
でサインインした場合の動画(音声なし)です。
利用部門に属するユーザーsato2007
でサインインした場合
sato2007
で組織の認証基盤にサインインし、フェデレーションブローカーにアイデンティティ情報の連携した場合、EC2のDescribe権限のみが要求可能となっています。EC2のDescribe権限を要求し、AWSマネジメントコンソールにシングルサインオンが完了すると、EC2インスタンスのリソース状況のみが表示される状態となっています。
下はsato2007
でサインインした場合の動画(音声なし)です。
まとめ
本記事で紹介した実装を自組織向けに適用し、組織の認証基盤とAWSマネジメントコンソールの認証統合を実現することで、AWS環境を用いた開発現場における以下の課題を解決することができます。
- ライフサイクルの管理
- メンバーの入社、退職、異動などのイベントにあったアイデンティティ情報の管理をする
- 職務分掌にあった権限の管理
- 開発者、運用者というメンバーの役割や所属部門にあった権限管理をする
いずれも、実現されていない場合、AWS IAM上のアカウント管理が破綻し、重大なセキュリティインシデントを招く可能性があります。よって、開発規模の大小によらず実装することを推奨します。
また、本来、組織内(閉じられたネットワーク内)でしか参照し得ないアイデンティティ情報を安全に組織外のサービスプロバイダに連携する手段として、本連載ではOpenID Connectを活用しました。連載を通してご紹介してきたOpenID Connectですが、アプリケーション(RP)は認証基盤(OP)から連携されたアイデンティティ情報を活用することで、ユーザーのアイデンティティ情報に関連する実装の効率性が格段に向上します。本記事で取り上げたフェデレーションブローカーはそれらの実装の一例ですが、アイデンティティ情報の連携ありきで動作するアプリケーションが増えることで、センシティブな情報(組織内のユーザーのアイデンティティ情報)が拡散しない環境もつくることができます。今後組織のネットワーク上の境界を超えて、アイデンティティ情報をインターネット上や他組織のサービスプロバイダに連携する実装をする機会があれば、OpenID Connectを使った実装をぜひ検討してみて頂ければと思います。
付録
今回実装したRelying Party(フェデレーションブローカー)のソースコードを公開します。ご自由にお使いください。
参考文献
- AWS Identity and Access Management(https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html)
- AWS Security Token Service(https://docs.aws.amazon.com/ja_jp/STS/latest/APIReference/API_AssumeRole.html)