API GatewayでAPIにIAM認証をかけて、Node.jsでSigV4署名ヘッダを作成してリクエストしてみる

CX事業本部の佐藤です。

API GatewayでIAM認証を設定して、Node.jsでSigV4署名を実装する機会がありましたので、知見としてまとめます。

API Gatewayの認証方式

まずは、API Gatewayの認証方式をおさらいしてみます。API Gatewayでは、APIに対して以下のような認証方式が存在します。

  • Cognitoを使った認証
  • サードパーティの認証基盤(Auth0など)とLambda Authorizerを使った認証
  • IAM認証

この記事では、この中のIAM認証に焦点を当てていきます。

そもそもSigV4署名とは?

正式名称は、AWS Signature V4 署名で、バージョン1〜4まであります。現在はバージョン4を使うのが推奨です。IAMのアクセスキーとハッシュ関数など用いて、AWSへのHTTPリクエストに署名する方法です。普段私たちが使っているAWS SDKなどは、この署名を内部的に設定してくれているため、使う側の私たちはこの署名を意識することはありません。私たちが、SigV4署名を意識する必要があるのは、SDKを使わないでAWSにリクエストする場合、例えば、AWS SDKがないプログミング言語を使用している場合や、今回のようにAPI GatewayにIAM認証を設定するような場合です。

わざわざ自前で実装しなくても、API GatewayのSDK生成機能を使えばいいのでは?

API GatewayにはSDKの生成機能があり、それを使うことでSDK側がSigV4署名を行ってくれます。ただこのSDKの生成機能は、開発、テスト中にAPIを変更するたびに生成し直す必要があり、生成後は、Lambda Layerの更新や開発リポジトリに組み込むなど、一手間必要なため、今回は自前で実装する選択肢にしました。

Node.jsでSigV4署名を作成する

API GatewayでIAM認証を設定する

まずは、API GatewayでIAM認証を設定したモックAPIを作成します。API Gatewayのコンソールに移動し、APIを作成します。

作成したら、ルートパスを選択しメソッドの作成をクリックします。

POSTを選択してチェックマークをクリックし、Mockを選択し、保存をクリックします。

これで、モックAPIができました。次にIAM認証を設定します。メソッドリクエストをクリックします。

認可で AWS_IAMを選択します。

これで、モックAPIのIAM認証の設定が完了しました。

SigV4署名ソースコード

次に、SigV4署名をしてHTTPリクエストをしてみます。AWS公式のドキュメントにあるように、SHA256やらHMACとかのハッシュ関数を使ってガリガリ書くのもいいんですが、aws-sdkaws-sdk/lib/coreパッケージにSigners.V4クラスがあり、これを使うことでSigV4署名を作ることができます

以下はNode.jsでSigV4署名を作成するコードです。


const core = require('aws-sdk/lib/core');
const aws = require('aws-sdk');

// アクセスキーとシークレットアクセスキーを設定
const accessKey = 'XXXX';
const secretKey = 'XXXX';
const credential = new aws.Credentials(accessKey, secretKey);

main();

function main() {
    
    // サービス名は、API GatewayのAPIの場合は、execiute-api固定です。
    const serviceName = "execute-api"; 
    
    // Signers.V4クラスのコンストラクタに渡すオプションを作成します。
    const options = {
        // api gatewayのURL
        url: 'https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/',
        headers: {}
    };
    
    // api gatewayのURLからホスト、パス、クエリストリングを抽出
    const parts = options.url.split('?');
    const host = parts[0].substr(8, parts[0].indexOf("/", 8) - 8);
    const path = parts[0].substr(parts[0].indexOf("/", 8));
    const querystring = parts[1];
    
    // V4クラスのコンストラクタの引数に沿う形でoptionsを作成
    const now = new Date();
    options.headers.host = host;
    options.pathname = () => path;
    options.methodIndex = 'post';
    options.search = () => querystring ? querystring : "";
    options.region = 'ap-northeast-1';
    options.method = 'POST';
    
    // V4クラスのインスタンスを作成
    const signer = new core.Signers.V4(options, serviceName);
    
    // SigV4署名
    signer.addAuthorization(credential, now);
    
    // 署名されたヘッダーを出力
    console.log(options.headers);
}

上記のコードを実行します。

node index.js

実行すると以下のようなHTTPヘッダの文字列がコンソール上に出力されます。これがSigV4の署名です。このヘッダーを使ってリクエストを送ることでIAM認証を突破することができます。

{
  host: 'XXXXXXX.execute-api.ap-northeast-1.amazonaws.com',
  'X-Amz-Date': '20200114T105609Z',
  Authorization: 'AWS4-HMAC-SHA256 Credential=XXXXXX, SignedHeaders=host;x-amz-date, Signature=XXXXXX'
}

動作確認

Postmanを使って、API Gatewayにリクエストを送ってみます。まずは、ヘッダに署名を付与しない状態でAPIを叩いてみます。

IAM認証をかけているため、Missing Authentication Token となりました。

次に、先ほど出力された署名ヘッダを入力して、リクエストを送ってみます。

今度は、Missing Authentication Token とはでませんでした。うまくいってそうです。

まとめ

最初は、AWS SDKにAPI Gatewayで作成したAPIを叩く関数があって、それを使えば簡単にできるのかなと思っていたんですが、そのような関数はありませんでしたので、今回のようにライブラリを使って実装しました。API GatewayにIAM認証をかける方に参考になれば幸いです。

参考

AWSAPI リクエストの署名

署名バージョン 4 を使用して AWS リクエストに署名する

完全なバージョン 4 署名プロセスの例 (Python)

aws-sdk V4クラス