この記事はハンズラボ Advent Calendar 2017の18日目です。
からっきーです。
今回はCognito User Poolに作成したグループ単位で認証済みユーザーがアクセス可能なAPIエンドポイントをコントロールしてみました。
API Gatewayには認証方式が3種類用意されているのですが、IAMロールベースで認可するサービスをコントロールしたい場合はIAM認証を選択します。
IAMで認証する場合、クライアントではsigV4署名を行い、それをリクエストに含める必要があります。
Pythonでそれをいちから実装する方法がこちらに載っていますがかなりつらそうなので今回はモジュールを使って実装しました。
使用したモジュール
capless/warrant
ユーザー名/パスワードを使ってCognitoユーザープール認証ができます。
jmenga/requests-aws-sign
sigV4を使用したAWSリソースへのアクセスをシンプルに実装できます。
リソースの設定
IAMロール
権限はこんな感じで設定しました。
UnauthenticatedRole
- 非認証ユーザーに割り当てられるロール
- すべてのAPIにアクセス不可
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"execute-api:Invoke"
],
"Resource": "*",
"Effect": "Deny"
}
]
}
authenticatedRole
- ログイン済みのユーザーに割り当てられるロール
-
/hello
はアクセス可 -
/admin
はアクセス不可
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"execute-api:Invoke"
],
"Resource": "arn:aws:execute-api:ap-northeast-1:000000000000:xxxxxxxx:/*/GET/hello",
"Effect": "Allow"
},
{
"Action": [
"execute-api:Invoke"
],
"Resource": "arn:aws:execute-api:ap-northeast-1:000000000000:xxxxxxxx:/*/GET/admin",
"Effect": "Deny"
}
]
}
adminGroupRole
- ログイン済みかつ管理者権限グループに所属しているユーザーに割り当てられるロール
- すべてのAPIにアクセス可
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"execute-api:Invoke"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
Cognito User Poolの設定
※説明を簡略にするためCognito User Poolは作成済みでユーザーが存在する前提で進めます。
グループを作成します。ここではclientGroup
とadminGroup
を作成しています。clientGroup
は一般ユーザーでadminGroup
は管理者ユーザーを入れます。
先ほどのIAMロールを以下のように紐付けます。
-
clientGroup
- authenticatedRole
-
adminGroup
- adminGroupRole
Cognito Identity Poolの設定
ロールの設定
ロールをそれぞれ以下のとおりに紐付けます。
- 「認証されていないロール」
UnauthenticatedRole
- 「認証されたロール」
authenticatedRole
認証プロバイダーの設定
- ユーザープール ID
- [作成済みのCognito User PoolのID]
- アプリクライアントID
- [作成済みのCognito User PoolのアプリクライアントID]
WebコンソールのIDプールの編集から「認証プロバイダー」を開き、下記のとおりに設定します。(CFnで書いてみたのですが、エラーがでるのでWebコンソールから設定しました)
- 「認証されたロールの選択」
- 「トークンからロールを選択する」
- 「ロールの解決」
- 「デフォルトの認証されたロールを使用する」にします。
APIを叩いてみる
検証で使用したコードはこちらです。
ログインしたあとcredentialsを発行しています。
その後/hello
と/admin
を叩いて、ステータスコードを出力しています。
# -*- coding: utf-8 -*-
import yaml
import requests
import boto3
from warrant.aws_srp import AWSSRP
from requests_aws_sign import AWSV4Sign
env = 'dev'
with open('serverless.env.yml') as file:
config = yaml.load(file)
ACCOUNT_ID = str(config[env]['account_id'])
REGION = config['region']
API_ENDPOINT = config[env]['api_endpoint']
USER_POOL_ID = config[env]['user_pool_id']
CLIENT_ID = config[env]['application_client_id']
IDENTITY_POOL_ID = config[env]['identity_pool_id']
def login(username, password):
# cognito user poolで認証してID Tokenを取得する
aws = AWSSRP(username=username, password=password, pool_id=USER_POOL_ID, client_id=CLIENT_ID)
tokens = aws.authenticate_user()
id_token = tokens['AuthenticationResult']['IdToken']
logins = {'cognito-idp.' + REGION + '.amazonaws.com/' + USER_POOL_ID: id_token}
client = boto3.client('cognito-identity', region_name='ap-northeast-1')
cognito_identity_id = client.get_id(
AccountId=ACCOUNT_ID,
IdentityPoolId=IDENTITY_POOL_ID,
Logins=logins
)
credentials = client.get_credentials_for_identity(
IdentityId=cognito_identity_id['IdentityId'],
Logins=logins
)
session = boto3.session.Session(
aws_access_key_id=credentials['Credentials']['AccessKeyId'],
aws_secret_access_key=credentials['Credentials']['SecretKey'],
aws_session_token=credentials['Credentials']['SessionToken'],
region_name=REGION
)
return session.get_credentials()
def get_requests(service, url, credentials):
headers = {"Content-Type": "application/json"}
auth = AWSV4Sign(credentials, REGION, service)
response = requests.get(url, auth=auth, headers=headers)
return response
def main():
username = 'testuser'
password = 'Password1234'
credentials = login(username, password)
service = 'execute-api'
# /hello
request_path = '/hello'
url = API_ENDPOINT + request_path
response = get_requests(service, url, credentials)
print('\nGET', request_path, response.status_code)
# /admin
request_path = '/admin'
url = API_ENDPOINT + request_path
response = get_requests(service, url, credentials)
print('\nGET', request_path, response.status_code)
return
if __name__ == '__main__':
main()
こちらが設定ファイルです。
defaultStage: dev
region: ap-northeast-1
dev:
profiles: myaws
user_pool_id: ap-northeast-1_xxxxxxxxxxx
application_client_id: xxxxxxxxxxxxxxxxxxxxxxxxxx
identity_pool_id: ap-northeast-xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx
account_id: 000000000000
api_endpoint: https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev
実行してみる
ここで使用しているtestuser
は現時点でclientGroupに入っています。
実行すると以下のように出力されます。
GET /hello 200
GET /admin 403
/admin
へのリクエストに対しては403 forbiddenがレスポンスされていることがわかります。
権限を変更して実行
続いて、testuser
をadminGroupに入れて同様に実行してみます。
GET /hello 200
GET /admin 200
/admin
へのリクエストが許可されるようになりました。
まとめ
実際のアプリでPythonからAPI Gatewayを叩くこともあるかもしれないですが、私の場合は主にAPI Gatewayを通したインテグレーションテスト用として使うことのほうが多そうです。
この機会に試せて良かったです。
明日はtuki0918さんです!!
参考