【aws】 keycloakとAPI Gatewayでアクセス制限

aws

今回は、API Gatewayを使う上で必須級の「アクセス制限」を実現したいと思います。

アクセス制限を実現する為の認証・認可(OIDC)には、Keycloakを使っていきます。

これまで敷居の高かった認証・認可の環境をAWS上で簡単に構築出来ちゃうんですよ!!!
ワクワクしませんか!?

あと、認証・認可がよくわかっていない人も、環境を構築して実際に触ることで理解が深まります!
是非チャレンジしてみてください。

やりたいこと

  • keycloakで認証を行い、アクセストークンを取得する。
  • アクセストークンを使って、API Gatewayへアクセスし、
  • Lambdaオーソライザでトークンの検証を行い、検証OKであれば、バックエンド処理を実行する。

aws構成図

やることリスト

  • 独自ドメインからEC2へのアクセス環境構築
  • keycloakの構築
  • Lambda(バックエンド)の作成
  • Lambdaオーソライザの作成
  • API Gatewayの作成

料金の確認

AWSサービスには、無料で使えるサービスもありますが、当然有料サービスもあります。

本章では、分かる範囲でAWSサービスの料金について書こうと思いますが、私の理解不足で漏れていることもあります。

各サービスを使う際は、料金がどのくらい掛かるかを理解した上で、自己責任で利用ください。

独自ドメインからEC2へのアクセス環境構築

独自ドメインからEC2へのアクセス環境を構築するには、以下記事を参照ください。

触って学ぶRoute53 ~独自ドメインでHTTPS通信~
今回は、独自ドメインを使ってHTTPS通信をやっていきたいと思います。Webサイトを立てる時は、自分で好きな名前のドメインを付けて、HTTPSで通信の暗号化を行いたいですよね!?それがこの記事で実現出来ちゃうんですよ!!!ワクワク...

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」のターゲットグループを作成する。

触って学ぶELB ~ALB構築~
今回は、負荷分散や高可用性を実現する為に必要な、Application Load Balancer(ALB)を構築していきます。ALBが動作しているかを確認する為に、EC2インスタンスを2つ構築し、それぞれに、表示内容の違うWeb...

リスナーの変更

ロードバランサ―のリスナー、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 ~Hello World表示~
今回は、awsのクラウドサービスの真骨頂である、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!\””}
が出力されたら成功になります!!!

最後に

如何でしたか?
座学で勉強するより更なる学びがあったのではないでしょうか。

タイトルとURLをコピーしました