AWS_IAM認証つきのAPIGatewayにAWS署名付きのリクエストを送る

AWS APIGatewayは[IAMでアクセスコントロール](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/permissions.html)をすることができます。
![apigateway.png](https://qiita-image-store.s3.amazonaws.com/0/31010/2e90f481-f489-cf7c-98fd-6694b7966f31.png)

AWS_IAMを設定したAPIGatewayのエンドポイントには[AWS署名バージョン4](https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4_signing.html)の仕様に沿ったAuthorizationヘッダを付加してリクエストする必要があります。
ところがこの署名を生成するプロセスって結構面倒なのですよね。そんなものAWSSDKで用意されているのだろうと思いきや、

https://github.com/aws/aws-sdk-js/blob/master/lib/signers/v4.js

これ、privateやん…というところが動機でした。

## npm request が AWS署名をサポートしていた

node.jsにはメジャーなHTTPClientライブラリとして、[request](https://www.npmjs.com/package/request)というnpmモジュールがあります。こいつのREADMEをよく眺めてみると、

> * aws - object containing AWS signing information. Should have the properties key, secret. Also requires the property bucket, unless you’re specifying your bucket as part of the path, or the request doesn’t use a bucket (i.e. GET Services). If you want to use AWS sign version 4 use the parameter sign_version with value 4 otherwise the default is version 2. Note: you need to npm install aws4 first.

サポートしているではありませんか。

```js
"use strict"

const co = require("co");
const request = require("request-promise-native");
const AWS = require("aws-sdk");

co(function*(){
  const cred = yield getCredential();
  console.log(cred.accessKeyId, cred.secretAccessKey, cred.sessionToken);
  return yield request({
    uri: " https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/trial/getinfo/1234",
    aws: {
      sign_version: 4,
      key: cred.accessKeyId,
      secret: cred.secretAccessKey,
    }
  })
}).then(console.log).catch(console.error);

function getCredential() {
  const credChain = new AWS.CredentialProviderChain();
  return new Promise((resolve, reject) => {
    credChain.resolve((err, creds) => {
      if(err) {
        reject(err);
      } else {
        resolve(creds);
      }
    })
  })
}
```

こんなので無事リクエストが受け入れられ、レスポンスが返ってきました。
サンプルソースは`Promise`,`co`,`function*`,`yield`をガシガシ使っており、Node.js 4.x前提です。

ちなみに、署名をつけていないと、

```
[StatusCodeError: 403 - "{\"message\":\"Missing Authentication Token\"}"]
```

署名が間違っていると、

```
[StatusCodeError: 403 - "{\"message\":\"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
```

こんな感じのエラーになります。

## しかし、少し不都合がある

二点困ったことがありました。

1. APIGatewayにカスタムドメインを設定しているケースではエラーになる
2. IAMロールでAPIのInvokeができるように設定したEC2インスタンスからの実行するケースではエラーになる

困ったのでプルリクエスト投げました。
https://github.com/request/request/pull/2339

上記がマージされるまでは`npm install newgyu/request`でnpmモジュールをインストールすると上記の問題に対処できます。


```js
"use strict"

const co = require("co");
const request = require("request-promise-native");
const AWS = require("aws-sdk");

co(function*(){
  const cred = yield getCredential();
  console.log(cred.accessKeyId, cred.secretAccessKey, cred.sessionToken);
  return yield request({
    uri: "https://api.newgyu.xyz/trial/getinfo/1234",
    aws: {
      sign_version: 4,
      key: cred.accessKeyId,
      secret: cred.secretAccessKey,
      //トークンの指定
      sessionToken: cred.sessionToken,
      //service,regionの指定
      service: "execute-api",    //APIGatewayの場合にはexecute-apiを指定する
      region: "ap-northeast-1"
    }
  })
}).then(console.log).catch(console.error);

function getCredential() {
  const credChain = new AWS.CredentialProviderChain();
  return new Promise((resolve, reject) => {
    credChain.resolve((err, creds) => {
      if(err) {
        reject(err);
      } else {
        resolve(creds);
      }
    })
  })
}
```