初期化関数init(6)


trust proxy

  • Expressアプリケーションの前にプロキシサーバを配置することに関連
  • expressの場合、req.ip, req.hostname, req.
  • クライアント情報は、プロトコル等により受信可能
  • プロキシサーバが設置すると、expressの観点から、そのプロキシサーバもクライアント
  • となる.
  • 信頼エージェントを設定することで、エージェントサーバの情報(ip、hostname、protocol)ではなく、実際のクライアントの情報を取得できます.

    テスト


    テスト条件

  • 80ポートを使用してnginxを起動し、すべてのリクエストを5000ポートに転送します.
  • brew install nginx
    /usr/local/etc/nginx/nginx.conf 설정 파일 변경
    brew services start nginx      
    nginx.confプロファイル
    worker_processes  1;
    
    events {
        worker_connections 1024;
    }
    
    http {
        server {
            listen 8080;
            server_name localhost;
    
            location / {
                proxy_pass http://YOUR_DOMAIN:YOUR_PORT;
            }
        }
    }
    https://kirillplatonov.com/2017/11/12/simple_reverse_proxy_on_mac_with_nginx/を参照
  • 500ポートでexpress,reqを起動します.ip, req.protocol, req.hostname, req.タイトルを確認しました.
  • テスト結果

  • の信頼エージェントが設定されていない場合は、
  • です.
    const express = require("express");
    
    const app = express();
    
    app.get("/", (req, res) => {
      console.log(req.ip);
      console.log(req.protocol);
      console.log(req.hostname);
      console.log(req.headers);
      res.send("hello");
    });
    
    app.listen(5000);
  • nginx(80ポート)から
  • にアクセス
    ::ffff:127.0.0.1 (req.ip)
    http (req.protocol)
    0.0.0.0 (req.hostname)
    { (req.headers)
      host: '0.0.0.0:5000',
      connection: 'close',
      'upgrade-insecure-requests': '1',
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1',
      'accept-language': 'ko-kr',
      'accept-encoding': 'gzip, deflate'
    }
  • Express(5000ポート)から
  • にアクセス
    ::ffff:192.168.206.153 (req.ip)
    http (req.protocol)
    192.168.206.141 (req.hostname)
    { (req.headers)
      host: '192.168.206.141:5000',
      'upgrade-insecure-requests': '1',
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1',
      'accept-language': 'ko-kr',
      'accept-encoding': 'gzip, deflate',
      connection: 'keep-alive'
    }
  • 信頼エージェントの設定時は
  • である.
    const express = require("express");
    
    const app = express();
    
    app.set("trust proxy", true);
    
    app.get("/", (req, res) => {
      console.log(req.ip);
      console.log(req.protocol);
      console.log(req.hostname);
      console.log(req.headers);
      res.send("hello");
    });
    
    app.listen(5000);
  • nginx(80ポート)から
  • にアクセス
    ::ffff:127.0.0.1 (req.ip)
    http (req.protocol)
    0.0.0.0 (req.hostname)
    { (req.headers)
      host: '0.0.0.0:5000',
      connection: 'close',
      'upgrade-insecure-requests': '1',
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1',
      'accept-language': 'ko-kr',
      'accept-encoding': 'gzip, deflate'
    }
  • Express(5000ポート)から
  • にアクセス
    ::ffff:192.168.206.153 (req.ip)
    http (req.protocol)
    192.168.206.141 (req.hostname)
    { (req.headers)
      host: '192.168.206.141:5000',
      'upgrade-insecure-requests': '1',
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1',
      'accept-language': 'ko-kr',
      'accept-encoding': 'gzip, deflate',
      connection: 'keep-alive'
    }

    に質問


    予測

  • nginxは、X-ForwardFor等のヘッダファイルを介して実クライアント情報
  • を伝達する.
  • expressは、信頼エージェントが設定か否かに応じて、X-Forward-Fro等のヘッダ情報を用いて実クライアントの情報
  • を設定.

    実際

  • X-Forward-DFOrなどのヘッダはnginxからexpress
  • に転送されない.
    ヘッダ(例えば
  • X-Forward-DFA)が設定されていないため、信頼エージェントの設定は意味のない
  • である.

    解決する

  • nginx.コンフィギュレーション
  • をconfに追加
     proxy_set_header        X-Real-IP       $remote_addr;
     proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header        X-Forwarded-Proto $scheme;
     proxy_set_header        X-Forwarded-Host  $host;
     proxy_set_header        X-Forwarded-Port  $server_port;
  • nginx(80ポート)から
  • にアクセス
    192.168.206.153 (req.ip)
    http (req.protocol)
    192.168.206.141 (req.hostname)
    { (req.headers)
      'x-real-ip': '192.168.206.153',
      'x-forwarded-for': '192.168.206.153',
      'x-forwarded-proto': 'http',
      'x-forwarded-host': '192.168.206.141',
      'x-forwarded-port': '80',
      host: '0.0.0.0:5000',
      connection: 'close',
      'upgrade-insecure-requests': '1',
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1',
      'accept-language': 'ko-kr',
      'accept-encoding': 'gzip, deflate'
    }
  • Express(5000ポート)から
  • にアクセス
    ::ffff:192.168.206.153 (req.ip)
    http (req.protocol)
    192.168.206.141 (req.hostname)
    { (req.headers)
      host: '192.168.206.141:5000',
      'upgrade-insecure-requests': '1',
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1',
      'accept-language': 'ko-kr',
      'accept-encoding': 'gzip, deflate',
      connection: 'keep-alive'
    }
  • 信頼エージェントを文字列(ipアドレス)、数値(hopcount)などに設定することもできます.
    -> app.set("trust proxy", "127.0.0.1");
    -> app.set("trust proxy", 0);
  • テストで学んだこと


    確認した情報を
  • 検索で検証した.
  • は、実際にどのような方法でクライアントの情報を取得することができるか.
  • X-Forwarded-For

  • 事実上の標準ヘッダ
  • HTTPエージェントまたはロードバランサは、実際のクライアントのIPアドレスを検証するために使用される.
  • X-Forwarded-For: <client>, <proxy1>, <proxy2>
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Forを参照

    コード解析


    lib/application

    app.defaultConfiguration = function defaultConfiguration() {
      var env = process.env.NODE_ENV || "development";
    
      // default settings
      this.enable("x-powered-by");
      this.set("etag", "weak");
      this.set("env", env);
      this.set("query parser", "extended");
      this.set("subdomain offset", 2);
      this.set("trust proxy", false);
    
      // trust proxy inherit back-compat
      Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
        configurable: true,
        value: true,
      });
    
      /**
       * this.set("trust proxy", false)설정시 @@symbol:trust_proxy_default : false로 설정이 되는데 다시 값을 설정하는 이유
       *
       * 기본 값이 세팅이 된 경우(app.init()에 의해 설정된 값)와 사용자가 임의로 설정(app.set("trust proxy", true))한 경우를 구분하기 위한것으로 추정됨
       *
       * 기본 값으로 설정된 경우  @@symbol:trust_proxy_default : true 이며
       * 그 이외의 경우에서 "trust proxy"를 설정한 경우
       * 굳이 "trust proxy"값을 바꿀때 @@symbol:trust_proxy_default의 값을 바꾸지 않는 한 @@symbol:trust_proxy_default : false가 설정이 됨
       *
       */
    
      debug("booting in %s mode", env);
    
      this.on("mount", function onmount(parent) {
        // inherit trust proxy
        if (
          this.settings[trustProxyDefaultSymbol] === true &&
          typeof parent.settings["trust proxy fn"] === "function"
        ) {
          delete this.settings["trust proxy"];
          delete this.settings["trust proxy fn"];
        }
    
        // inherit protos
        setPrototypeOf(this.request, parent.request);
        setPrototypeOf(this.response, parent.response);
        setPrototypeOf(this.engines, parent.engines);
        setPrototypeOf(this.settings, parent.settings);
      });
    
      // setup locals
      this.locals = Object.create(null);
    
      // top-most app is mounted at /
      this.mountpath = "/";
    
      // default locals
      this.locals.settings = this.settings;
    
      // default configuration
      this.set("view", View);
      this.set("views", resolve("views"));
      this.set("jsonp callback name", "callback");
    
      if (env === "production") {
        this.enable("view cache");
      }
    
      Object.defineProperty(this, "router", {
        get: function () {
          throw new Error(
            "'app.router' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app."
          );
        },
      });
    };

    lib/utils/compeilTrust

    /**
     * val이 true인 경우 항상 true를 리턴하는 함수를 리턴하고
     * 숫자인 경우 설정된 hopcount보다 낮은경우 true를 리턴하는 함수를
     * 문자열이고 "127.0.0.1, 192.168.0.1"형식의 여러 ip주소가 들어오는 경우
     * 문자열을 나눈다.
     */
    
    exports.compileTrust = function (val) {
      if (typeof val === "function") return val;
    
      if (val === true) {
        // Support plain true/false
        return function () {
          return true;
        };
      }
    
      if (typeof val === "number") {
        // Support trusting hop count
        return function (a, i) {
          return i < val;
        };
      }
    
      if (typeof val === "string") {
        // Support comma-separated values
        val = val.split(/ *, */);
      }
    
      return proxyaddr.compile(val || []);
      /**
       * val이 undefined, null, false인 경우 빈 배열을 넘기고
       * 그 이외의 경우 val을 넘겨준다.
       *
       * val: undefined || false || null
       * -> return () => false;
       * 그 이외의 경우 입력돤 ip주소가 val을 통하여 입력된 ip주소와 비교하여 그 결과를 리턴하는 함수를 리턴한다.
       */
    };