LoginSignup
11
2

Keycloakを使ってAPI GatewayでAPIをアクセス制限する

Last updated at Posted at 2023-12-20

本記事は、「Keycloakを使ってAPI GatewayでAPIをアクセス制限する」の記事を元に、Amazon API Gatewayを使ったアクセス制限(認可)として、クライアントアプリ向けのAPIサーバーをAmazon API GatewayとAWS lamdaで構築して、どのようにアクセス制限が行えるのか検証する。

本記事でやること

  1. Keycloakサーバ設定
  2. Lambda関数作成
  3. API Gateway作成
  4. 動作検証

Amazon API Gatewayとは

Amazon API Gatewayは、様々な規模の REST、HTTPなどのAPIを作成、公開、維持、モニタリング、セキュア化できる AWS のフルマネージドサービスのことである。
世間一般的なAPI Gatewayとは何か、についてはこちらの記事で説明されているので本記事では割愛する。

今回の構築する構成

以下のような環境を構築する。
認証についてはKeycloakを利用して、
APIのアクセス制御に関してはAPI GatewayのLambda オーソライザー機能を利用してトークン(JWT)の検証処理を行う。

構成図.png

Keycloakサーバ設定

レルムを作成

Realm nameをapi-gatewayとしてレルムを作成する。
1_レルム作成.PNG

ユーザー作成

作成したレルムに所属するユーザーとして、今回は検証用に以下の2つのユーザーを作成する。

ユーザー名 用途
testuser1 ロール未所属のユーザー
testuser2 api-gateway-roleのロールに所属するユーザー

2_ユーザー作成.PNG

ロール作成&ユーザー追加

ロール名をapi-gateway-roleとしてロールを作成し、作成済みのtestuser2ユーザーを作成したロールに所属させる。
3_ロール追加.PNG

Keycloakの公開鍵情報取得

左のメニューからレルムの設定タブを押下して、公開鍵情報を確認する。RS256公開ボタンを押下することで、公開鍵情報を表示される。
4_公開鍵①_編集.PNG
4_公開鍵②.PNG
公開鍵はLambdaオーソライザーのトークン検証処理の実装時に利用するため、後で確認できるようにメモしておく。

Lambda関数作成

AWSのLambdaサービスの画面を開き、関数の作成ボタンから以下の2つのLambda関数を作成する。ランタイムなどは何でもよいが、今回はNode.jsで作成した。

API業務処理関数作成

業務用の関数はほぼデフォルトのままとし、以下の通り返却するJson文字列だけ変更した。

lamda業務関数
export const handler = async(event) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Get resource!!!'),
    };
    return response;
};

オーソライザー用関数作成

オーソライザー関数についてはこちらをベースに、トークン検証用にjsonwebtokenを利用する形で実装した。

Lambdaオーソライザー関数
import * as jsonwebtoken from 'jsonwebtoken';
export const handler =  function(event, context, callback) {
    // トークンを取得
    var token = event.authorizationToken.replace("Bearer ", "");
    
    // Keycloak側の公開鍵
    const publicKey = "-----BEGIN PUBLIC KEY-----\r\n"
                      + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx..."
                      + "\r\n-----END PUBLIC KEY-----";

    // トークンの検証
    jsonwebtoken.verify(token, publicKey, (err, decoded) => {
        if (err) {
            // 検証NG (403 Error)
            console.log(err);
            callback(null, generatePolicy('user', 'Deny', event.methodArn));
        } else {
            // 検証OK
            var roles = decoded.realm_access.roles
            if(roles.includes('api_gateway_test_role')) { 
                callback(null, generatePolicy('user', 'Allow', event.methodArn));
            } else {
                // 検証NG (403 Error)
                console.log('権限なし');
                callback(null, generatePolicy('user', 'Deny', event.methodArn));
            }   
        }
    });
};

// ポリシーを作成
var generatePolicy = (principalId, effect, resource) => {
    var authResponse = {};
    
    authResponse.principalId = principalId;
    if (effect && resource) {
        var policyDocument = {};
        policyDocument.Version = '2012-10-17'; 
        policyDocument.Statement = [];
        var statementOne = {};
        statementOne.Action = 'execute-api:Invoke'; 
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement[0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }

    return authResponse;
}

オーソライザー関数では以下のような処理を行っている。

  1. リクエストヘッダからBearerを除去したトークンを取得
  2. Keycloakの公開鍵を用いてトークンを検証
  3. 検証成功時、デコードしたトークンからroleの情報を取り出してapi_gateway_test_roleのロールを持っているか確認
    ※トークン検証NGの時などは、エラーとして403応答が返却
  4. ロール保持時、Invokeの権限を付与したポリシーを作成してコールバック
    ※ロール非保持の場合、権限をDenyに設定してコールバック

本記事では公開鍵を直接Lambda関数に埋め込んだが、JWKsエンドをKeycloakで設定することで都度参照するようにすることも可能

API Gateway作成

API作成

REST APIを選択してAPIを作成する。API名以外はデフォルトの設定値のままとする。
6_API作成.PNG

リソース&メソッド作成

API一覧から作成したAPIを選択して、アクションボタン → リソース作成ボタンを押下してリソースの作成画面を開く。
7_リソース作成②.PNG

リソース名を入力して、リソース作成ボタンを押下すると、以下のように作成したリソースがAPIに追加される。
7_リソース作成③.PNG

次にメソッドを作成する。
作成したリソースを選択した状態で、アクションボタン → メソッドの作成ボタンを押下すると、リソースの下にメソッドのセレクトボックスが表示される。GETを選択してリストの右にある✓ボタンを押下するとメソッドの設定画面が開かれるので作成したLambda関数等を設定していく。
7_リソース作成⑤.PNG

Lamda関数の欄に事前に作成済みの関数名を入力して保存ボタンを押下する。
これでAPIのtest_resourceパスのGETメソッドに作成したLambda関数が統合された。

APIデプロイ

作成したAPIを外部からアクセスできるようにデプロイを行う。
アクションボタン → メソッドの作成ボタンを押下してデプロイするステージを選択する。ステージが未作成の場合は、新規作成選択およびステージ名を入力してからデプロイボタンを押下する。
8_デプロイ①.PNG
デプロイすると以下のようにデプロイしたリソースがステージに含まれる。
8_デプロイ②.PNG

テスト実行

ここで作成したAPIを実行して動作確認してみる。現時点ではアクセス制限をしていない状態のため、API Gatewayは単純なプロキシーをする動きのみとなっている。
APIを実行するクライアントとしてPostmanを利用して、以下のようにtest_resourceパスのGETでリクエストを送信する。

9‗テスト実行.PNG
リクエストにはアクセストークンなど一切設定していないがひとまずAPIの正常疎通が確認できた。

オーソライザー作成

最後にAPI Gatewayにオーソライザーを作成する。
API Gatewayの左側のメニューからオーソライザーを選択して、新しいオーソライザーの作成を押下してオーソライザーを作成する。
オーソライザー①.PNG

設定項目 設定内容
名前 任意でOK
Lambda関数 事前に作成したオーソライザー用Lambda関数
Lambda呼び出しロール 未設定でOK
トークンのソース トークンとして取得するHTTPヘッダの名称
トークン検証 トークンのバリデーション
認可のキャッシュ 任意で設定

トークンソースのソースは実際のHTTPヘッダの名称を指定するが、Lambda関数内で取得するときはauthorizationTokenとなる。

続けて作成したオーソライザーをAPIに設定する。
リソースからオーソライザーを設定するメソッドを選択して。メソッドリクエストの部分を押下する。
オーソライザー②.PNG
メソッドリクエストの設定画面が開くので、認可の部分で作成したオーソライザーを設定する。
オーソライザー③.PNG
ここまで完了したらオーソライザーを適用したAPIを再度デプロイして準備完了。

動作検証

今回作成したAPIはapi_gateway_test_roleを持っているユーザーのみアクセスを許可するようにオーソライザーを設定しているので、対象のロールを持っていないユーザー、持っているユーザーのトークンを用いてAPIを実行し、期待通りに制限できているか検証する。

まず、対象のロールを持っていないtestuser1で取得したトークンでAPIを実行する。
トークン取得についてはKeycloak標準であるaccount_connsoleなどでログインして取得し、PostmanのAuthorizationヘッダに付与して実行する。
その結果、以下のように必要なロールを保持していないため認可されず、403応答でレスポンスが返却される。
API_NG.PNG
Lambdaのログを確認するとしっかり検証失敗時に埋め込んだログが出力されているため、オーソライザーとして作成したLambda関数で検証されていることが確認できる。
API_NG②.PNG

次に、対象のロールを持っているtestuser2で取得したトークンでAPIを実行する。
先ほど同様に取得したトークンをPostmanのAuthorizationヘッダに付与して実行すると、以下のように必要なロールを保持しているため認可され、200応答でレスポンスが返却される。
API_OK.PNG
以上でAmazon API GatewayとKeycloakを使ってAPIのアクセス制限を確認することができた。

最後に

今回は簡易的な検証として、トークン(JWT)にある属性値の一致をみるだけのアクセス制限(認可)でした。実際にOIDCとしての認可ではトークン・インストロスペクション・エンドポイントへトークンを送り、現在アクティブかどうかや権限情報を取得するような動きとすべきかと思います。そのあたりについては機会があればまた検証してみたいと思います。

参考資料

11
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
2