Nginx - lyonwang/TechNotes GitHub Wiki

Nginx HTTP 499 Error 參考:服务器排障 之 nginx 499 错误的解决

proxy_ignore_client_abort on; #让代理服务端不要主动关闭客户端的连接。

注:只在做反向代理的时候加入,作为其他服务器的时候,关闭为好,默认设置是关闭的!

架構上使用 Nginx 的目的 Test Scenario and Dev Isolation 將 QA 第三方 Endpoint 導入開發區,區隔第三方開發與測試 Logging & Tracking 內外部網路存取 Access Log 集中 Trace Log (Trace/Span) Application Gateway 應用層網路存取控管 (TCP/UDP, HTTP...) IP 黑白名單 Cross Origin Resource Sharing (CORS) 安裝版本選擇 因應現有黑白名單控管,以及後續擴展需求,選擇具有 Lua script 功能的 Openresty 版本:

https://openresty.org/en/

https://hub.docker.com/r/openresty/openresty/

Nginx Lua Module: https://github.com/openresty/lua-nginx-module#system-environment-variable-support

安裝步驟 Openresty https://moonbingbing.gitbooks.io/openresty-best-practices/openresty/install_on_centos.html

yum package install sudo yum install yum-utils sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo sudo yum install openresty sudo yum install openresty-resty sudo yum install openresty-opm Nginx.service for "systemctl start/stop/restart nginx" /usr/lib/systemd/system/nginx.serivce [Unit] Description=Nginx Service For Openresty After=network.target

[Service] Type=forking ExecStart=/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/conf -c nginx.conf ExecReload=/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/conf -c nginx.conf -s reload ExecStop=/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/conf -c nginx.conf -s stop ExecReopen=kill -USR1 $(cat /usr/local/openresty/nginx/logs/nginx.pid) privateTmp=true

[Install] WantedBy=multi-user.target Nginx config file location /usr/local/openresty/nginx/conf/nginx.conf opm (Openresty Package Manager) sudo curl -k https://raw.githubusercontent.com/openresty/opm/master/bin/opm > /usr/local/openresty/bin/opm sudo chmod +x /usr/local/openresty/bin/opm Install IPIP Module Install ipip module sudo opm install pintsized/lua-resty-http sudo opm get linsir/lua-resty-ipip # use root

黑白名單 Lua script 參考 Source: https://github.com/linsir/lua-resty-ipip

Zeus IP 白名單: IPInfo_WhileList.csv Zeus 地區名單(ipip資料庫): mydata4vipday.datx Lua IP 白名單: ip_whitelist.csv Lua IP 地區黑名單: region_blacklist.csv Lua CORS allow 設定檔: cors_allow.list sudo cp 1,3,4,5 to /ipip

sudo cp 2 to /usr/local/openresty/site/lualib/resty/ipip/data

http { ...

設定引用 ipip 函數庫路徑

lua_package_path "/usr/local/openresty/site/lualib/resty/ipip/?.lua;;";
# Lua 客製程式區塊
init_by_lua_block {
  local ipip = require "resty.ipip.client"
  cjson = require "cjson"
  local opts = {
    path = '/usr/local/openresty/site/lualib/resty/ipip/data/mydata4vipday.datx',
    token = '97dc010a0793d97bad1a7d7eb98d5ab87ef0f8ee',
    timeout  = '2000',
  }
  ipipc = ipip:new(opts)

  -- 檢查 IP 白名單: 有在白名單則回傳 true
  function checkIpWhiteList(ipaddr, data_path)
      local found = false
      if not data_path or not ipaddr then
          ngx.log(ngx.ERR, data_path)
          --ngx.say(data_path)
          return false
      end
      local ip1, ip2, ip3, ip4 = string.match(ipaddr, "(%d+).(%d+).(%d+).(%d+)")
      local ip_uint32 = ip1 * 256 ^ 3 + ip2 * 256 ^ 2 + ip3 * 256 + ip4
      --ngx.say(ip_uint32)
      local file, err = io.open(data_path, "r")
      if file == nil then
          ngx.log(ngx.ERR, data_path)
          --ngx.say(string.format("file is nill => %s", data_path))
          return false
      else
        for line in file:lines() do
          local f, t = line:match("(%d+),(%d+),(.*)")
          if ip_uint32 >= tonumber(f) and ip_uint32 <= tonumber(t) then
            found = true
            break
          end
        end       
        file:close()
      end
      return found
  end

  -- IP 地區資料庫黑名單: 有在黑名單或為私有IP則回傳 true
  function checkRegionBlackList(ipaddr)
    local ipipc = ipipc
    local cjson = cjson
    -- 查詢 ipip 資料庫
    local res, err = ipipc:query_file(ipaddr)
    if res == nil then
      ngx.log(ngx.ERR, "ipipc:query_file("..ipaddr..") fail")
      return false, "ipipc:query_file("..ipaddr..") fail";
    else
      ngx.log(ngx.NOTICE, cjson.encode(res))
      ngx.log(ngx.NOTICE, string.format("iso_2: %s", res[12]))
    end
    -- 檢查是否為私有 IP
    local isPrivIP = is_ip_private(ipaddr)
    if isPrivIP == true then
      return true, cRes
    end
    -- 取得地區黑名單
    local blist = {}
    local blist_file, err = io.open("/ipip/region_blacklist.csv", "r")
    if blist_file == nil then
      ngx.log(ngx.ERR, "/ipip/region_blacklist.csv")
    else
      local blist_str = blist_file:read("*all")
      ngx.log(ngx.NOTICE, blist_str)
      blist = string.split(blist_str, ",")
      ngx.log(ngx.NOTICE, cjson.encode(blist))
      blist_file:close()
    end
    -- 檢查黑名單
    -- Blacklist: nation code(TW, HK, US, PH, SG)
    for key, value in ipairs(blist) do
      if res[12] == value then
        return true, res
      end
    end
    return false, res
  end


  function is_ip_private(ipaddr)
    local pri_addrs = {
                        { 167772160, 184549375 }, -- 10.0.0.0 ~ 10.255.255.255 : single class A network
                        { 2886729728, 2887778303 }, -- 172.16.0.0 ~ 172.31.255.255 : 16 contiguous class B network
                        { 3232235520, 3232301055 }, -- 192.168.0.0 ~ 192.168.255.255 : 256 contiguous class C network
                        { 2851995648, 2852061183 }, -- 169.254.0.0 ~ 169.254.255.255 : Link-local address also refered to as Automatic Private IP Addressing
                        { 2130706432, 2147483647 } -- 127.0.0.0 ~ 127.255.255.255 : localhost
                      }
    local o1,o2,o3,o4 = ipaddr:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" )
    local num = 2^24*o1 + 2^16*o2 + 2^8*o3 + o4 -- ip to long
    for k,v in pairs(pri_addrs) do
      if num >= v[1] and num <=v[2] then
        return true
      end  
    end
    return false
  end

  string.split = function(s, p)
      local rt= {}
      string.gsub(s, "[^"..p.."]+", function(w) table.insert(rt, w) end )
      return rt
  end

  function getClientIP(remoteIP, realIP, xForwardedFor)
      local retIP = remoteIP
      if xForwardedFor then
        local iplist = string.split(xForwardedFor, ", ")
        if iplist[1] then
          retIP = iplist[1]
        end
      end
      if realIP then
        retIP = realIP
      end
    return retIP
  end

  -- 跨來源資源共享 Cross-Origin Resource Sharing (CORS) 白名單
  function processCORS()
      varyHeaders = { "Origin",
                      "Access-Control-Request-Method",
                      "Access-Control-Request-Headers" }
      allowMethods = { "GET",
                       "POST",
                       "PUT",
                       "DELETE",
                       "OPTIONS" }
      allowHeaders = { "Accept",
                       "Authorization",
                       "Cache-Control",
                       "Content-Type",
                       "DNT",
                       "If-Modified-Since",
                       "Keep-Alive",
                       "Origin",
                       "User-Agent",
                       "X-Requested-With",
                       "X-Forwarded-For",
                       "X-REQUEST-ID",
                       "X-REQUEST-START-UTCTIME" }
      -- Response headers for CORS: Vary 表示要求客戶端要傳送給 Server 的 CORS Request headers
      ngx.header["Vary"] = table.concat(varyHeaders, ",")
      if (ngx.var.http_origin) then
        if (isInCORSList(ngx.var.http_origin)) then
          ngx.header["Access-Control-Allow-Origin"] = ngx.var.http_origin
          ngx.header["Access-Control-Allow-Credentials"] = "true"
          ngx.header["Access-Control-Allow-Methods"] = table.concat(allowMethods, ",")
          ngx.header["Access-Control-Allow-Headers"] = table.concat(allowHeaders, ",")
          if(ngx.var.request_method == "OPTIONS") then
            -- Tell client that this pre-flight info is valid for 20 days
            ngx.header["Access-Control-Max-Age"] = 1728000
            ngx.header["Content-Type"] = "text/plain charset=UTF-8"
            ngx.header["Content-Length"] = 0;
            ngx.exit(204);
          end
        end
      end
  end

  function trim(s)
    return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
  end

  function string.startwith(String,Start)
    return string.sub(String,1,string.len(Start))==Start
  end

  function isInCORSList(origin)
    local cors_list = {}
    local cors_list_file, err = io.open("/ipip/cors_allow.list", "r")
    if cors_list_file == nil then
      ngx.log(ngx.ERR, "/ipip/cors_allow.list")
    else
      for line in cors_list_file:lines() do
        cors_list[table.getn(cors_list) + 1] = trim(line)
      end
      cors_list_file:close()
    end
    for key, value in ipairs(cors_list) do
      if origin:startwith("http://"..value) then
        return true
      end
    end
    return false
  end

}

# Test site
server {
    listen 80;
    server_name 127.0.0.1 172.20.5.27 10.101.5.154 www.lyon.com www.lyon1.com;

    charset utf-8;
    #default_type text/plain;
    #root /usr/local/openresty/nginx/html;

    add_header X-REQUEST-ID $request_id; # Return to client
    add_header X-REQUEST-START-UTCTIME $time_iso8601; # Return to client

    location /tracelog {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        echo "X-REQUEST-ID: $request_id, X-REQUEST-START-UTCTIME: $time_iso8601"
        proxy_set_header X-REQUEST-ID $request_id; # Pass to app server
        proxy_set_header X-REQUEST-START-UTCTIME $time_iso8601; # Pass to app server
    }

    location /cors {
        access_by_lua_block {
            processCORS()
            ngx.say('<html><body><a href="http://www.lyon1.com/cors1">To lyon1</a><img src="http://www.lyon.com/image/logo.png" /></body></html>')
        }
    }

    location /test {
        set $clientIP $remote_addr;
        set $result "non";

        access_by_lua_block {
            ngx.var.clientIP = getClientIP(ngx.var.remote_addr, ngx.var.http_x_real_ip, ngx.var.http_x_forwarded_for)
            res, err = ipipc:query_file(ngx.var.clientIP)
            ngx.var.result = cjson.encode(res)
        }

        echo "clientIP: $clientIP, X-Forwarded-For: $http_x_forwarded_for, remote_addr: $remote_addr";
        echo $result;
    }

    location /ipcheck {
        set $a 1;

        # rewrite_by_lua also worked
        access_by_lua_block {
            ngx.var.clientIP = getClientIP(ngx.var.remote_addr, ngx.var.http_x_real_ip, ngx.var.http_x_forwarded_for)
            local is_pass = true
            is_white = checkIpWhiteList(ngx.var.clientIP, "/ipip/ip_whitelist.csv")
            if not is_white then
              local is_ip_in_balck_list = checkIPBlackList(ngx.var.clientIP)
              if is_ip_in_balck_list == true then
                is_pass = false
              else
                is_black = checkRegionBlackList(ngx.var.clientIP)
                if is_black == true then
                  is_pass = false
                end
              end
            end
            if is_pass then
                ngx.var.a = "http://127.0.0.1/pass"
            else
                ngx.var.a = "http://127.0.0.1/access_denied"
            end
        }

        #echo $a;
        proxy_pass $a;
    }

    location /pass {
        echo "Pass";
    }

    location /access_denied {
        echo "Denied";
    }
}

... }

** NOTICE log 需要在 nginx.conf 開啟才能在 error.log 中看見

Configuration files

CentOS 7 /etc/nginx/nginx.conf /etc/nginx/conf.d/*.conf nginx conf 組織結構與維護方式: Nginx_Gateway_Replacement.zip

nginx.conf 檔案內容 ... http { include services/*.conf; } ... Services Folder: 包含 service conf ([service_name].conf: 含有 http section 可填的設定值) server { map $request_uri $up_url { default ""; include uri_map/[service name].map } server { listen [port number]; include server_list/[service_name].slist

    location / {
        proxy_pass $up_url;
    }
}

} server_list/[service name].slist Regular expression [domain name] ... uri_map/[service name].map Regular expression value ...

⚠️ **GitHub.com Fallback** ⚠️