今回は、API Gatewayを使う上で必須級の「アクセス制限」を実現したいと思います。
アクセス制限を実現する為の認証・認可(OIDC)には、Keycloakを使っていきます。
これまで敷居の高かった認証・認可の環境をAWS上で簡単に構築出来ちゃうんですよ!!!
ワクワクしませんか!?
あと、認証・認可がよくわかっていない人も、環境を構築して実際に触ることで理解が深まります!
是非チャレンジしてみてください。
やりたいこと
- keycloakで認証を行い、アクセストークンを取得する。
- アクセストークンを使って、API Gatewayへアクセスし、
- Lambdaオーソライザでトークンの検証を行い、検証OKであれば、バックエンド処理を実行する。
aws構成図
やることリスト
- 独自ドメインからEC2へのアクセス環境構築
- keycloakの構築
- Lambda(バックエンド)の作成
- Lambdaオーソライザの作成
- API Gatewayの作成
料金の確認
AWSサービスには、無料で使えるサービスもありますが、当然有料サービスもあります。
本章では、分かる範囲でAWSサービスの料金について書こうと思いますが、私の理解不足で漏れていることもあります。
各サービスを使う際は、料金がどのくらい掛かるかを理解した上で、自己責任で利用ください。
独自ドメインからEC2へのアクセス環境構築
独自ドメインからEC2へのアクセス環境を構築するには、以下記事を参照ください。
Dockerインストール
keycloak構築・設定
$ sudo yum install docker
$ sudo systemctl start docker && sudo systemctl enable docker
$ sudo usermod -a -G docker ec2-user
EC2にログインしなおすと、docker コマンドが使えるようになります。
keycloak構築
$ docker run -d \
-p 8080:8080 \
-e PROXY_ADDRESS_FORWARDING=true \
-e KEYCLOAK_USER=admin \
-e KEYCLOAK_PASSWORD=password \
--name keycloak_test \
jboss/keycloak
HTTPSとコンテナの関連付け
keycloakへアクセスし、設定を行うにはhttps通信が必要になります。
443ポートで入ってきたリクエストを上記keycloakコンテナの8080ポートに渡す為に、ターゲットグループの追加とリスナーの変更が必要になります。
ターゲットグループの追加
以下サイトの「設定~ターゲットグループ~」の章を参考に、ポートを「8080」のターゲットグループを作成する。
リスナーの変更
ロードバランサ―のリスナー、443ポートのターゲットグループを、上記作成のターゲットグループに置き換える。
設定
ログインのIDとPWは、上記コンテナ作成時に設定した、USER, PASSWORD を使用します。
レルムの作成
レルムは、ユーザ・クライアント・ロールなどを管理します。
左上 > Add realm ボタンを押下。
レルム名は、wakuwaku で作成
ユーザ・パスワードの作成
Users > Add user ボタンを押下。
ユーザ名は、shisihmaru で作成
Credentials タブを押下。
好きなパスワードを設定
ロールの作成
Roles > Add role ボタンを押下。
wakuwaku-admin で作成
ユーザにロール付与
Users > View all users > Edit > Role Mappings ボタンを押下。
上記で作成したロールを付与する。
クライアントの作成・設定
クライアントは、Keycloakとやり取りしてユーザを認証したり、トークンを取得するエンティティを指します。
Clients > Create ボタンを押下。
Client IDは、test で作成
以下設定に変更します。
- Access Type: confidential
- Valid Redirect URIs: 評価用なので、存在しないURL(例:https://canmakewakuwaku.commm/)
クライアントシークレットの取得
Clints > 任意 Client ID > Credentials タブ
Secret の内容をメモしてください。
アクセストークンを取得する際に使用します。
レルムの公開鍵取得
Realm Setting > Keys タブ
RS256 の Public key ボタンを押下し、公開鍵をメモしてください。
アクセストークンの検証に使用します。
Lambda(バックエンド)の作成
バックエンドとなる処理はなんでもよいので、
以下サイトを参考にLambdaを作成してください。
Lambdaオーソライザの作成
オーソライザのlambdaを作成する際、
ランタイムは「Python 3.8」、
アーキテクチャは「x86_64」に設定ください。
他の組み合わせだと、jwtライブラリ関連でpython実行時にエラーが出ました。
コード
import logging
import jwt
sys.path.append('/opt/packages')
print(jwt.__file__)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
public_key = '<レルムの公開鍵>'
SECRET = "-----BEGIN PUBLIC KEY-----\n" \
+ public_key \
+ "\n-----END PUBLIC KEY-----"
print("hello world ")
def lambda_handler(event, context):
logger.info(event)
token = event['authorizationToken']
token = token.replace("Bearer ", "")
try:
payload = jwt.decode(token, SECRET, algorithms=['RS256'])
print("payload")
logger.info(payload)
return generatePolicy('user', 'Allow', event['methodArn'], payload)
except jwt.DecodeError as e:
logger.error(e)
return generatePolicy('user', 'Deny', event['methodArn'], None)
return response.text
def generatePolicy(principalId, effect, resource, context):
authResponse = {}
authResponse['principalId'] = principalId
if effect and resource:
policyDocument = {
'Version': '2012-10-17',
'Statement': [
{
'Sid': 'authorizer',
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': "*"
}
]
}
authResponse['policyDocument'] = policyDocument
# if context:
# authResponse['context'] = context
logger.info(authResponse)
return authResponse
レイヤの設定
以下理由で、ライブラリはレイヤに配置する。
- 上記コード内でライブラリを使おうとすると、ライブラリのサイズが大きくなるとコンソール画面上でコードが書けなくなる
- 暗号化用のライブラリ
cryptography
を使うには、アーキテクチャにx86_64を使う必要がある(コードのアーキテクチャはarmを使っているので合わない)
ライブラリのzip化
mkdir packages
cd packages
pip install pyjwt -t .
pip install cryptography -t .
cd ..
zip -r authorizer packages/
レイヤー作成
上記zipファイル使ってレイヤを作成します。
Lambdaサービス > レイヤー > レイヤーの作成 ボタンを押下。
レイヤー追加
Lambdaサービス > 関数 > authorizer > (画面最下部の)レイヤーの追加 ボタンを押下
API Gatewayの作成
APIの作成
API Gateway サービス > API > APIを作成 ボタンを押下。
API タイプを選択
「REST API」を選択し、「構築」ボタンを押下。
その他設定
上記画面の様に設定し、「APIの作成」ボタンを押下する。
リソースの作成
上記画面の「リソースの作成」ボタンを押下し、リソース名を入力し、リソースを作成する。
メソッドの追加
先ほど作成したリソースを選択し、
「メソッドの作成」ボタンを押下。
上記画面の「メソッドの作成」ボタンを押下し、「GET」を選択し、右のチェックボタンを押す。
メソッドのセットアップ
上記の通り設定し、「保存」ボタンを押下。
APIのデプロイ
アクション > APIのデプロイ ボタンを押下。
上記画面の様に設定し、「デプロイ」ボタンを押下する。
オーソライザーの作成
「新しいオーソライザ―の作成」ボタンを押下。
上記設定を行い、「作成」ボタンを押下。
トークンのソース:トークンのHTTPヘッダ名:Authorization
トークン検証:トークンのバリデーション:^Bearer [-_0-9a-zA-Z.]+$
オーソライザーの紐づけ
APIメソッドに、オーソライザーを設定する。
「メソッドリクエスト」を押下。
「認可」に、先ほど作成したオーソライザ―を選択する。
APIを変更したので、再度「APIのデプロイ」を実施する。
動作確認
認可フローには、認可コードフローを利用します。
認可コード取得
認可エンドポイントから認可コードを取得する。
・ブラウザで、以下URLを実行します。
(以下2か所の <> の内容は適宜変更ください。)
https://<ドメイン名>/auth/realms/wakuwaku/protocol/openid-connect/auth?client_id=test&redirect_uri=<リダイレクトURL>&response_mode=query&response_type=code
・ユーザアカウントを入力します。
「ユーザ・パスワードの作成」の章で設定した内容です。
・認証が成功すると、ブラウザのアドレスバーに認可コードが返ってきます。
https://<リダイレクトURL>?session_state=17f16wa0-4rc1-4w98-99c2-173f80646ede&code=ca168209-95b7-49c0-9e27-57b0d1d24159.17f16ca0-46c1-4898-99c2-174f80696ede.06ddb66c-d990-4a61-be7a-23863070bb57
アクセストークン取得
トークンエンドポイントから、認可コードを使ってアクセストークンを取得する。
注意点として、認可コードの有効期間は短いです。認可コード取得~アクセストークン取得の作業は手短に。
・以下コマンドを実行
(以下5か所の <> の内容は適宜変更ください。)
curl -XPOST -d "grant_type=authorization_code&code=<認可コード>&redirect_uri=<リダイレクトURL>&client_id=<クライアント>&client_secret=<クライアントシークレット>" -H "Content-Type: application/x-www-form-urlencoded" "https://<ドメイン名>/auth/realms/wakuwaku/protocol/openid-connect/token"
・成功すると、以下の通りアクセストークンが返ってきます。
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ5d1BYR2ZwNk4wcHFBbXFIbjNKcDhDeDhSTHdqZzh2Vl9MR2kxYWVTVDg4In0.eyJleHAiOjE2OTI5MjE2NzYsImlhdCI6MTY5MjkxODA3NiwiYXV0aF90aW1lIjoxNjkyOTE3MTYzLCJqdGkiOiJiY2U3NjQ0Yi1jMjViLTRkOWMtOWJkOS1mNzQ3MTY4YzkyODEiLCJpc3MiOiJodHRwczovL2Nhbm1ha2V3YWt1d2FrdS5uZXQvYXV0aC9yZWFsbXMvd2FrdXdha3UiLCJzdWIiOiI5NGYzZjVlOC1lYmFjLTQ2MmEtOGVhZS0yNTA0Njk0ODMyMjIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6IjE3ZjE2Y2EwLTQ2YzEtNDg5OC05OWMyLTE3NGY4MDY5NmVkZSIsImFjciI6IjAiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsid2FrdXdha3UtYWRtaW4iXX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjE3ZjE2Y2EwLTQ2YzEtNDg5OC05OWMyLTE3NGY4MDY5NmVkZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoic2hpc2hpbWFydSJ9.MNqCXYUF50a0_71qXxqxlb2HQVE0FF7oX5SVuPWCu2Jq0lZIojjs5fYWLFkslKijxNF5gm5pOAGs4o_x4Z0KnJlTb3zDqoEFy3qQRB7L58w2AC90GRSxgMfzd9NuQgr7o7pV5iAPR0u_VX3t2PyY-Q-9ECJKDMr5Gs24TM8EmNbw1oxJj-3XiBfB6QSXwoNy7rRatuZ8aAxSXdC8y_Mixl4ehpeFmDRzhjIPShlYGIM7BDlgBPi-YaVOygWdavQgEenq2WaQBXNmY1gJb7eInWEsOxEL_dbasVRxIYDeerxm9p4-x0CV3BtbE9J3_AwGHzG5QHjcrbx_xGIc_uNiwA","expires_in":3600,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1ZjJjYjM2Ni1jMzBiLTQ5NDgtYjYyYi0zNTk0MGM4YmU2ODMifQ.eyJleHAiOjE2OTI5MTk4NzYsImlhdCI6MTY5MjkxODA3NiwianRpIjoiYjI1YWZhMzctYWM4Ni00OGRhLWE1ZGQtNDQwZjQ4MTBlYWJhIiwiaXNzIjoiaHR0cHM6Ly9jYW5tYWtld2FrdXdha3UubmV0L2F1dGgvcmVhbG1zL3dha3V3YWt1IiwiYXVkIjoiaHR0cHM6Ly9jYW5tYWtld2FrdXdha3UubmV0L2F1dGgvcmVhbG1zL3dha3V3YWt1Iiwic3ViIjoiOTRmM2Y1ZTgtZWJhYy00NjJhLThlYWUtMjUwNDY5NDgzMjIyIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InRlc3QiLCJzZXNzaW9uX3N0YXRlIjoiMTdmMTZjYTAtNDZjMS00ODk4LTk5YzItMTc0ZjgwNjk2ZWRlIiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiMTdmMTZjYTAtNDZjMS00ODk4LTk5YzItMTc0ZjgwNjk2ZWRlIn0.ih-zLpGNgir4Zb0_i1PMdlrRJh7lICd3nZ2uVj0YaR8","token_type":"Bearer","not-before-policy":0,"session_state":"17f16ca0-36c1-4898-99c2-174f80696ede","scope":"email profile"}
API Gatewayへのアクセス確認
いや~、長かったですね!!!
ついに最後の確認です。
・以下コマンドを実行
curl -H "Authorization: Bearer <アクセストークン>" "<API GatewayのURL>"
結果として
{“statusCode”: 200, “body”: “\”Hello from Lambda!\””}
が出力されたら成功になります!!!
最後に
如何でしたか?
座学で勉強するより更なる学びがあったのではないでしょうか。