淡々と備忘録を綴る

IT周りの行き詰まったところやメモを備忘録として記録します

API GatewayでAPIにIAM認証をかけて、SigV4署名を作成してPOSTを実行する

API GatewayでIAM認証を設定して、SigV4署名を実装する機会があったので、備忘録としてまとめる。

IAM認証とは

AWS APIGatewayのAPIを叩くときに使用する認証方法のうちの一つ。事前に権限を絞ったIAMユーザを用意しておき、APIの実行の際にIAMユーザの認証を要求する。認証されたIAMユーザが当該のAPIの実行を認可されている場合に、正常にAPIが応答を返す。

SignV4署名とは

上記IAM認証を実現するための手法。IAMユーザの認証にはアクセスキーとシークレットキーが必要だが、シークレットキーを外部にハードコーディングする訳にもいかないのでSignV4署名を発行して、署名をヘッダに入れることで認証を行う。

注意点

  • Signv4署名のハッシュはリクエスト全体のハッシュを計算する
    SignV4署名の際、IAMユーザの認証のみではなくリクエスト全体の認証も同時に行う。
    つまり、APIを叩く際のリクエストすべてに対して署名を発行することで、実際にAPIを叩くときには署名発行時と全く同じリクエストであるかを確認しているような挙動となる。
    このリクエスト全体というのは、メソッドに種類や(POSTやPUTSメソッドを使用する場合)ペイロードの中身も含まれる。(最初このことを知らなくて多くの時間を無駄にした。。。)
    署名発行の際にはちゃんとペイロードの情報を受け渡しておこうねという訳である。
  • 署名の際に受け渡すデータは文字列であることが必要
    実際にPOSTメソッドでAPIを叩くとき、ペイロードJSON形式で作成することが多くあると想定される。
    上記のペイロードを署名発行の際に受け渡すにあたって、JSONオブジェクト形式では署名発行時にエラーが発生する。 署名発行時にはJSON.stringfyなどを使用して文字列形式にしてから署名を作成することでこのエラーを回避した。

    ソースコード

    以下に、Node.jsで作成したSginv4署名のソースコードを記載する。 なお、この記事/コードを書くにあたって以下のブログを参考にした。 dev.classmethod.jp

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

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

exports.handler = async (event) =>{
    let method = event.method;
    let url = event.url;
    const res = main(url,method,event.requestBody);
    return res; 
};
function main(apiurl,method,requestBody) {
    // サービス名は、API GatewayのAPIの場合は、execiute-api固定です。
    const serviceName = "execute-api"; 
    // Signers.V4クラスのコンストラクタに渡すオプションを作成します。
    const options = {
        // api gatewayのURL
        url: apiurl,
        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 = method;
    options.search = () => querystring ? querystring : "";
    options.region = 'ap-northeast-1';
    options.method = method;
    if(method == "POST"){
         options.body = requestBody;
         options.headers['Content-Type'] = "application/json";
         }
    
    // V4クラスのインスタンスを作成
    const signer = new core.Signers.V4(options, serviceName);
    // SigV4署名
    signer.addAuthorization(credential, now);
    //署名されたヘッダーを出力
    console.log(options.headers);
    const response = {
        statusCode: 200,
        // body: JSON.stringify(array2responseData(responseArray))
        body: options.headers
    };
    return response;
}

このlamdaにPOSTする内容は以下の通り

{
  "method": "XXXX",
  "url": "XXXXXXXXX"
  "requestBody":XXXX
}