11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Serverlessな環境でexpressを動かすときに気をつけたいProxyに関する設定

Posted at

昨日,思わぬところでハマってしまったので,共有いたします.

状況

aws_serverless_express越しに,aws lambda上で,expressを動かしたときに,Set-Cookieヘッダが発行されないという問題に数時間悩まされました.

https://github.com/microsoft/TypeScript-Node-Starter/ を参考に,ほぼ同様の構成で,

  • express
  • express-session
  • passport
    • providerは,microsoft

でログイン機能をつけてみたのですが,oauth callbackが無事に呼ばれて,User認証できても,いっこうにブラウザにsession cookieが発行されず,ログインできないのでした.

結論としては,cookieのoptionに,secure: trueをつけていたが,aws lambdaで実行しているexpressがlistenしているconnnectionは,httpsでないため,cookieが発行されない,ということでした.

どういうことか

一般に,AWS上で,SAMアプリケーションを作ると,Client -> API Gateway -> Lambdaという経路をたどることになります.このうち,Client -> API Gatewayについてはもちろん,API Gateway -> LambdaもHTTPSでの通信になっています.このため,CookieのSecure属性をtrueにしても,問題なく動作すると考えていました.

ところが,aws_serverless_express を使っているというところに思わぬ見落としがありました.

そこで,aws_serverless_expressがどういう処理をやっているのか覗いてみましょう.まず,APIGateway Lambda Integrationによって,http通信の情報をAPIGatewayEventというオブジェクトに格納して,Lambda関数が実行されます.

aws_serverless_expressは,このAPIGatewayEventをその名もapApiGatewayEventToHttpRequestという関数に渡して,Node.jsの,http.requestのoption引数に変換します.

そして,同一のプロセス内にexpress serverに起動して(!),そこに向けてリクエストを投げているのです.

つまり,lamdbaまでの経路はすべてhttpsであっても,最後の最後,lambdaの実行プロセスからexpressのhandlerが実行される部分で,httpになっていたのです.

通常のproxyであれば,X-Forward-Forに,自分自身のIPを追加したりしますが,aws_serverless_expressは厳密には,proxyではないため,X-Forward-Forを変更することなく,ヘッダ情報をほぼそのまま横流ししているため,HTTP Request/Respnseをデバッグしているだけでは見つけづらかった問題でした.

対応

原因がわかったので,対策に移りましょう.では,どうすればSecure属性を保ったまま,Cookieを発行できるかというと,

  1. express全体の設定として,trust proxyを設定する

つまり,

const app = express();
app.set("trust proxy", true);

または,

  1. express-sessionのcookieに関する設定で,trustProxyを設定する
const app = express();
app.use(
    session({
        // 本稿に関係のないオプションは非表示
        cookie: {
            proxy:true,
            secure: true,
            maxAge: 24 * 60 * 60 * 1000,
        },
    })
);

をします.aws_serverless_expressを使うのなら,1を選択するのがおすすめですが,影響範囲が読めない場合2でもいいでしょう.

trustProxytrueにすると,どういう挙動になるか,expressのreq.protocolの実装を見てみましょう.

// https://github.com/expressjs/express/blob/4.17.1/lib/request.js#L307-L323
defineGetter(req, 'protocol', function protocol(){
  // (筆者コメント: aws_serverless_express越しなので,ここは,'http')
  var proto = this.connection.encrypted
    ? 'https'
    : 'http';
  var trust = this.app.get('trust proxy fn');

  // (筆者コメント: trustProxyがFalseの場合,ここで'http'が返され,secureじゃないねとなる)
  if (!trust(this.connection.remoteAddress, 0)) {
    return proto;
  }

  // (筆者コメント: trustProxyがTrueの場合,'X-Forwarded-Proto'を読みに行ってくれて,結果'https'となる)
  // Note: X-Forwarded-Proto is normally only ever a
  //       single value, but this is to be safe.
  var header = this.get('X-Forwarded-Proto') || proto
  var index = header.indexOf(',')

  return index !== -1
    ? header.substring(0, index).trim()
    : header.trim()
});

つまり,trustProxytrueにすると,今直接通信しているconnectionの状態ではなくて,HTTPのX-Forwarded-Protoヘッダを信頼し,その設定を元に,secureかどうか判断するようになるというわけです.

結果,aws_serverless_expressな環境でもSecureなcookieを発行することができました.

おしまい.

11
7
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
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?