SAML2.0とAD FSを用いてフェデレーテッドAPIとCLIアクセスを実装する方法
AWSはSAML (Security Assertion Markup Language) 2.0を用いた認証フェデレーションをサポートしています。 SAMLを用いて、あなたは自身のIDプロバイダーを統合するためにAWSアカウントを構成することができます。 設定後、フェデレーテッドユーザーはあなたの組織のIdPにより認証と認可を受けることになり、AWS管理コンソールへのシングルサインオン(SSO)を用いることができるようになります。 これはユーザーに新たなユーザー名とパスワードを覚えてもらうことをなくすだけではなく、管理者のためにID管理を合理化することにもなります。 これは、もしあなたのフェデレーテッドユーザーがAWS管理コンソールにアクセスしたいときにはよいですが、ではフェデレーテッドユーザーがAWS CLIを利用したかったり、プログラム的にAWS APIを呼び出したいときにはどうでしょうか?
このブログ記事では、ユーザーのためにどのようにフェデレーテッドAPIやCLIアクセスを実装するか紹介しようと思います。 例としてAWS Python SDKの利用といくつかの追加のクライアントサイドでのインテグレーションコードを提供します。 もしあなたがこの種のアクセスを必要とするフェデレーテッドユーザーをお持ちなら、このソリューションを実装することであなたは称賛を勝ち得ることができるはずです。
最終的にどのようになるのか簡単に見ることから始めてみましょう。
janedoe@Ubuntu64:/tmp$ ./samlapi.py
Username: AD\janedoe
Password: ****************
Please choose the role you would like to assume:
[ 0 ]: arn:aws:iam::012345678987:role/ADFS-Administrators
[ 1 ]: arn:aws:iam::012345678987:role/ADFS-Operators
Selection: 1
---------------------------------------------------------------
Your new access key pair has been stored in the aws configuration
file /home/janedoe/.aws/credentials under the saml profile.
Note that it will expire at 2015-05-26T17:16:20Z.
After this time you may safely rerun this script to refresh your
access key pair.
To use this credential call the aws cli with the --profile option
(e.g. aws --profile saml ec2 describe-instances).
---------------------------------------------------------------
Simple API example listing all s3 buckets:
[<Bucket: mybucket1>, <Bucket: mybucket2>, <Bucket: mybucket3>,
<Bucket: mybucket4>, <Bucket: mybucket5>]
上の出力の中では厳密には何が行われているのでしょうか。
- ユーティリティはフェデレーテッドユーザーにActive Directoryの認証情報を入力するように促します。 これらの認証情報は構成されているIdPに対してセキュアに認証、認可されるために利用されます。
- ユーティリティは戻ってきたSAMLの認証応答(アサーション)を調査し、ユーザーが引き受けることを認可されているIAMロールを決定します。 ユーザーが望むロールを選択した後、ユーティリティはAWS Security Token Service (STS)を使い一時的なセキュリティ証明書を受け取ります。
- ユーティリティはこの認証情報をユーザーのローカルのAWS認証情報ファイルに自動的に書き込み、ユーザーはAWS APIやCLIを呼び出すことを始められます。
この例を見てあなたの組織のためにカスタマイズを行えば、AWS Identity and Access Management (IAM) によるコントロールを管理するとともに、あなたは組織の認証情報を用いてAWS APIとCLIの自動化と力を活用することができるようになります。
この記事はMicrosoft Active Directory Federation Services (AD FS)を利用することにフォーカスしていますが、もしあなたが違うIDプロバイダーを利用したい場合でもがっかりすることはありません。 基本的なパターンは他の一般のIdPとも機能するはずです。
はじめてみましょう
この記事にならうためには、あなたは以下のことが必要となります。
- あなたの組織の認証情報のみを用いてコンソールにアクセスできるよう正しくAWSアカウントと統合されたAD FS 。もしこのセットアップのインストラクションが必要であれば、 Enabling Federation to AWS using Windows Active Directory, ADFS, and SAML 2.0を参照ください。
- お手持ちのワークステーションにインストールされた最近のバージョン(2.36以降)のAWS Python SDK
- あなたの希望のリージョンとアウトプットのフォーマットを決める以下の最低限の内容を持つAWS認証情報ファイル(例:~/.aws/credentials)
[default]
output = json
region = us-west-2
aws_access_key_id =
aws_secret_access_key =
重要な注意:AWSのアクセスキーのペアは上記の仕組みの中では設定されていません。始めのAWS STSの呼び出しは信頼されたIdPにより返されたSAMLアサーションにより認証されるからです。その後の全てのAPI/CLI呼び出しは返されたSTSトークンの中のキーペアにより認証されます。 更なる詳細は「SAML を使用してフェデレーティッドユーザーに AWS コンソールへのアクセスを許可する」を参照ください。
まず、あなたはコアPythonディストリビューション以外の2つのモジュール、特にbeautifulsoup4とrequests-ntlmをインストールする必要があります。 これにはいくつか方法がありますが、Python 2.7.9以降に含まれるpipユーティリティだと非常に容易に行うことができます。あなたは単純に以下の2つのコマンドを実行すればよいだけです。
pip install beautifulsoup4
そして
pip install requests-ntlm
あなたは以下のスクリーンショットと似た出力を得るはずです。
AWSのフェデレーションのプロセスはIdP-initiated loginとして知られるものを利用します。 準備のステップの最後で、あなたはこのログインの開始に使用される特別なURLを決める必要があります。 あなたのベーシックなIdP-initiated login URL(AWS管理コンソールを含む様々なSAML Relying Parties (RP)へのフェデレーテッドアクセスに使われるもの)で開始してください。 この例では、私はhttps://<fqdn>/adfs/ls/IdpInitiatedSignOn.aspxのフォームを持つURLでAD FS 2.0を使っています。 もし私がこのURLをブラウザのアドレスフィールドに入れると、以下の画像に示されるようなサイトを選択する画面が現れます。
あなたが必要となるURLを作成するために、もともとのIdP-initiated login URLにクエリ文字列?loginToRp=urn:amazon:webservicesを連結します。 結果としてURLはhttps://<fqdn>/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservicesという形になります。このURLはセーブしてください。なぜならば、idpentryurlとして参照されることになるからです。この連結されたURLをブラウザのアドレスフィールドに入れた場合、あなたは依然としてAD FSのIdP-initiated loginプロセスにたどり着きます。しかしながら、あなたはサイトを選択するページをバイパスし、認証の後ここでの演習で必要となるAWS管理コンソールのURLにまっすぐ進むことになります。もしあなたが興味があるようでしたら、Microsoftのwebサイトにクエリ文字列について書かれています。
重要な注意:URLの中のホスト名の大文字使用によく注意してください。AD FSはこれを認証処理として使用しており、大文字使用のミスマッチは認証が失敗する原因となります。
コードのレビュー
今、あなたは上に書かれている"はじめてみましょう" セクションのステップを終え、フェデレーションによるAPIとCLIアクセスを可能とするインテグレーションコードの編集を開始する準備ができました。 何が行われているのかについて、1回について1まとまりの処理について説明をしますので、一緒についてくることができると思います。 まず、いくつかの基本的なインポートと変数をセットアップをしましょう。
#!/usr/bin/python
import sys
import boto.sts
import boto.s3
import requests
import getpass
import ConfigParser
import base64
import xml.etree.ElementTree as ET
from bs4 import BeautifulSoup
from os.path import expanduser
from urlparse import urlparse, urlunparse
from requests_ntlm import HttpNtlmAuth
##########################################################################
# Variables
# region: The default AWS region that this script will connect
# to for all API calls
region = 'us-west-2'
# output format: The AWS CLI output format that will be configured in the
# saml profile (affects subsequent CLI calls)
outputformat = 'json'
# awsconfigfile: The file where this script will store the temp
# credentials under the saml profile
awsconfigfile = '/.aws/credentials'
# SSL certificate verification: Whether or not strict certificate
# verification is done, False should only be used for dev/test
sslverification = True
# idpentryurl: The initial URL that starts the authentication process.
idpentryurl = 'https://<fqdn>/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices'
##########################################################################
あなたの特定のリージョン(e.g., us-west-2, us-east-1, etc.)や、フォーマットの好み (i.e., json, text, or table)に合わせて前述のコード中の変数を修正し、idpentryurlのためにこの記事の前のセクションで記述したものを持ってきます。
基本的な環境のセットアップと共に、ユーザー名とユーザーの認証情報を標準入力より受け取るようにします。
他の認証情報のフォームをどのようにサポートするかについてはこの記事で後程説明します。
# Get the federated credentials from the user
print "Username:",
username = raw_input()
password = getpass.getpass()
print ''
はじめのprint "Username",に続いているコンマはバグのように見えるかもしれませんが、これはprintステートメントに改行コードが付くことを防ぐためのPythonのちょっとした作法です。 出力にパスワードが表示されてしまうことを防ぐgetpass()メソッドの使い方にも気を付けてください。
次に以下の例にあるように、Pythonのrequestモジュールを用いて認証情報を整理し、IdPへのhttpsリクエストとして組み立てます。 認証が成功すると、AD FSから返される応答にはSAMLのアサーションが含まれます。
# Initiate session handler
session = requests.Session()
# Programatically get the SAML assertion
# Set up the NTLM authentication handler by using the provided credential
session.auth = HttpNtlmAuth(username, password, session)
# Opens the initial AD FS URL and follows all of the HTTP302 redirects
response = session.get(idpentryurl, verify=sslverification)
# Debug the response if needed
#print (response.text)
この時点で私達はIdPの応答を無事得ていますので、もはや必要ではなくなったユーザーのユーザー名とパスワードを保管している変数を上書きして使わないようにしましょう。 高機能言語のPythonではCのように直接メモリーを操作することはできませんが、これは認証情報が使われないことを確実にするよい処理となります。
# Overwrite and delete the credential variables, just for safety
username = '##############################################'
password = '##############################################'
del username
del password
以前に書いたように、私達が必要とするSAMLのアサーションはIdPの応答の中にあります。以下のコードはそれを展開するためにBeautifulSoupモジュールを使用しています。
# Decode the response and extract the SAML assertion
soup = BeautifulSoup(response.text.decode('utf8'))
assertion = ''
# Look for the SAMLResponse attribute of the input tag (determined by
# analyzing the debug print lines above)
for inputtag in soup.find_all('input'):
if(inputtag.get('name') == 'SAMLResponse'):
#print(inputtag.get('value'))
assertion = inputtag.get('value')
この部分は単純にSAMLResponseという名前のものを見つけるまで全てのHTMLのinputタグを繰り返し確認しています。Base64-encodedのアサーションにはこのタグのvalue属性が含まれるようになります。先に進む前に、私達はもう一段深くSAMLの認証応答自身を見て、ユーザーに認可されたロールのリストを取り出す必要があります。このリストは以下のコードに見られるようにhttps://aws.amazon.com/SAML/Attributes/Roleという名前のSAML属性に保管されています。
# Parse the returned assertion and extract the authorized roles awsroles = [] root = ET.fromstring(base64.b64decode(assertion)) for saml2attribute in root.iter('{urn:oasis:names:tc:SAML:2.0:assertion}Attribute'): if (saml2attribute.get('Name') == 'https://aws.amazon.com/SAML/Attributes/Role'): for saml2attributevalue in saml2attribute.iter('{urn:oasis:names:tc:SAML:2.0:assertion}AttributeValue'): awsroles.append(saml2attributevalue.text) # Note the format of the attribute value should be role_arn,principal_arn # but lots of blogs list it as principal_arn,role_arn so let's reverse # them if needed for awsrole in awsroles: chunks = awsrole.split(',') if'saml-provider' in chunks[0]: newawsrole = chunks[1] + ',' + chunks[0] index = awsroles.index(awsrole) awsroles.insert(index, newawsrole) awsroles.remove(awsrole)
上にあげたはじめのループでは、私はPythonのxml.etree.ElementTreeモジュールを使いSAMLアサーションをパースしています。 アサーションは特別見やすいという訳ではありませんが、特別な名前空間や名前はあなたが探しているものを比較的簡単に見つけることができます。
二番目のループでは、コードは単にロールの中に含まれるAmazon Resource Names(ARNs)が正しい順番であることを確認するためにプロアクティブなエラープルーフを行っています。
これでユーザーが引き受けることを許可されたロールがわかりました。 次にユーザーがどのロールを引き受けたいかユーザーに確認します。
# If I have more than one role, ask the user which one they want,
# otherwise just proceed
print ""
if len(awsroles) > 1:
i = 0
print "Please choose the role you would like to assume:"
for awsrole in awsroles:
print '[', i, ']: ', awsrole.split(',')[0]
i += 1
print "Selection: ",
selectedroleindex = raw_input()
# Basic sanity check of input
if int(selectedroleindex) > (len(awsroles) - 1):
print 'You selected an invalid role index, please try again'
sys.exit(0)
role_arn = awsroles[int(selectedroleindex)].split(',')[0]
principal_arn = awsroles[int(selectedroleindex)].split(',')[1]
else:
role_arn = awsroles[0].split(',')[0]
principal_arn = awsroles[0].split(',')[1]
もしユーザーが一つだけしかロールを持たなければ、それが自動的に使用されます。 そうでない場合、ユーザーはシンプルにリストされたものから使用したいロールを選択するよう求められます。
これで少し楽になってきました。 私達は既にSAMLアサーションを受け取って様々なARNを取り出しているので、AWSの一時的な認証情報をリクエストするために通常のAWS STSサービスをただ呼び出せばよいだけです。 これは本当にこのユーティリティ全体のポイントとなります。 以下のシンプルなAssumeRolewithSAML APIコールのためのパラメーターを用意するためにここまでの全ての努力を行ってきたのです。
# Use the assertion to get an AWS STS token using Assume Role with SAML
conn = boto.sts.connect_to_region(region)
token = conn.assume_role_with_saml(role_arn, principal_arn, assertion)
重要な注意:構成された信頼できるIdPからのSAMLアサーションはこのAPIコールのための認証情報として機能します。
今私達はAWS STSから認証情報を手に入れました。 次のステップは以下のコードの箇所にあるようにAWS認証情報ファイルの特定のプロファイルにこれらの認証情報を入れることです。 そうすることにより、あなたは認証情報が期限切れになるまで、いくつでもAPIやCLIコールのためにこの一時的な認証情報を使うことができるようになります。
# Write the AWS STS token into the AWS credential file
home = expanduser("~")
filename = home + awsconfigfile
# Read in the existing config file
config = ConfigParser.RawConfigParser()
config.read(filename)
# Put the credentials into a specific profile instead of clobbering
# the default credentials
if not config.has_section('saml'):
config.add_section('saml')
config.set('saml', 'output', outputformat)
config.set('saml', 'region', region)
config.set('saml', 'aws_access_key_id', token.credentials.access_key)
config.set('saml', 'aws_secret_access_key', token.credentials.secret_key)
config.set('saml', 'aws_session_token', token.credentials.session_token)
# Write the updated config file
with open(filename, 'w+') as configfile:
config.write(configfile)
このコードの箇所では、AWS認証情報ファイルを検索したり、ファイルの中を読んだり、'saml'と呼ばれているプロファイルを追加、アップデートしたり、ファイルを書き戻すのに変数の組み合わせを使用しています。
最後に、フェデレーテッドユーザーに何が起きたのかについての情報を提供します。
# Give the user some basic info as to what has just happened
print '\n\n----------------------------------------------------------------'
print 'Your new access key pair has been stored in the AWS configuration file {0} under the saml profile.'.format(filename)
print 'Note that it will expire at {0}.'.format(token.credentials.expiration)
print 'After this time you may safely rerun this script to refresh your access key pair.'
print 'To use this credential call the AWS CLI with the --profile option (e.g. aws --profile saml ec2 describe-instances).'
print '----------------------------------------------------------------\n\n'
もし、あなたがCLIベースの自動化をしているのでしたら、次のセクションの"全てを取りまとめる"にスキップすることができます。あるいは、もし自動化がAPIベースでしたら、コネクションを確立する際、一時的な認証情報をシンプルに用いることができます。
# Use the AWS STS token to list all of the S3 buckets
s3conn = boto.s3.connect_to_region(region,
aws_access_key_id=token.credentials.access_key,
aws_secret_access_key=token.credentials.secret_key,
security_token=token.credentials.session_token)
buckets = s3conn.get_all_buckets()
print 'Simple API example listing all s3 buckets:'
print(buckets)
実際には、これらの追加のAPIコールは上で設定した認証情報のプロファイルを用いた別のスクリプトに置いたほうがずっとよいのですが、この練習のポイントはAWS STSから受けた認証情報がAPIコールの認証に使われるということになります。
すべてをまとめてみる
このコードのすべてひとまとめにすると、あなたは以下の出力にあるものを見ることになります。
janedoe@Ubuntu64:/tmp$ ./samlapi.py
Username: AD\janedoe
Password: ****************
Please choose the role you would like to assume:
[ 0 ]: arn:aws:iam::012345678987:role/ADFS-Administrators
[ 1 ]: arn:aws:iam::012345678987:role/ADFS-Operators
Selection: 1
---------------------------------------------------------------
Your new access key pair has been stored in the aws configuration file /home/janedoe/.aws/credentials under the saml profile.
Note that it will expire at 2015-05-26T17:16:20Z.
After this time you may safely rerun this script to refresh your access key pair.
To use this credential call the aws cli with the --profile option (e.g. aws --profile saml ec2 describe-instances).
---------------------------------------------------------------
Simple API example listing all s3 buckets:
[<Bucket: mybucket1>, <Bucket: mybucket2>, <Bucket: mybucket3>, <Bucket: mybucket4>, <Bucket: mybucket5>]
上の出力では正確には何をみているのでしょうか?
- ユーティリティはフェデレーテッドユーザーにActive Directoryの認証情報を入れるように求めます。これらの認証情報は設定されているIdPに対してユーザーの認証と認可をセキュアに行うために使われます。
- ユーティリティは返されたSAMLアサーションを調べ、ユーザーが引き受けることを許可されたIAMロールを決定します。ユーザーが望むロールを選択すると、ユーティリティは一時的な認証情報を取得するためにAWS Security Token Service (STS)を利用します。
- ユーティリティは自動的にこの認証情報をローカルのAWS認証情報ファイルに書き込むと、ユーザーはAWS APIやCLIコールを発行できるようになります。
以下の出力に見られるように、samlプロファイルをシンプルに参照することで続くAPIやCLIコールを発行することができます。
janedoe@Ubuntu64:/tmp$ aws --profile saml --output text s3api list-buckets
BUCKETS 2014-09-09T17:11:48.000Z mybucket1
BUCKETS 2014-11-03T18:27:35.000Z mybucket2
BUCKETS 2014-08-13T02:45:28.000Z mybucket3
BUCKETS 2015-01-14T17:53:47.000Z mybucket4
BUCKETS 2015-05-19T19:23:25.000Z mybucket5
OWNER myowner 128abcedf111aa33cc44ddee5512345abcd6fff4eed
結果として、 SAMLのブラウザ指向の性質は、ちょっとしたコードとエンジニアリングを必要としますが、それで目標が達成されます: APIとCLIコールのフェデレーションベースの認証を可能にします!
他の認証プロバイダーや認証メカニズムの利用
もしあなたがAD FSとは別のIdPをお使いの場合、このブログ記事にあなたのソリューションが含まれていないことにがっかりしないでください。 私がブラウザの挙動をエミュレートするのにPythonモジュールを用いた統合技術を選んだ理由は、それがそのIdPソリューションにも拡張できるはずだったからです。
同様に、もしあなたがKerberosやX.509証明書、多要素認証のような他の認証メカニズムを使用している場合、Python requestモジュールには他のハンドラがあります。 これらの他のハンドラは、あなたが基礎的な練習をした後、上の例を改良するのに利用することができます。
この記事がお役にたつことを願い、フィードバックを頂くことを楽しみにしています。
- Quint (日本語訳は高田智己が担当しました)
コメント