This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 03f0912 feat(real-ip): implement the first version (#4813)
03f0912 is described below
commit 03f09127f301165ca98e6b887004a60058055fb0
Author: 罗泽轩 <[email protected]>
AuthorDate: Fri Aug 13 17:00:00 2021 +0800
feat(real-ip): implement the first version (#4813)
---
apisix/plugins/real-ip.lua | 100 ++++++++
conf/config-default.yaml | 1 +
docs/en/latest/config.json | 1 +
docs/en/latest/plugins/client-control.md | 2 +-
docs/en/latest/plugins/gzip.md | 2 +-
.../plugins/{client-control.md => real-ip.md} | 42 ++--
t/admin/plugins.t | 2 +-
t/core/config.t | 2 +-
t/debug/debug-mode.t | 1 +
t/plugin/real-ip.t | 267 +++++++++++++++++++++
10 files changed, 396 insertions(+), 24 deletions(-)
diff --git a/apisix/plugins/real-ip.lua b/apisix/plugins/real-ip.lua
new file mode 100644
index 0000000..f0da0bb
--- /dev/null
+++ b/apisix/plugins/real-ip.lua
@@ -0,0 +1,100 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local is_apisix_or, client = pcall(require, "resty.apisix.client")
+local str_byte = string.byte
+local str_sub = string.sub
+
+
+local schema = {
+ type = "object",
+ properties = {
+ source = {
+ type = "string",
+ minLength = 1
+ }
+ },
+ required = {"source"},
+}
+
+
+local plugin_name = "real-ip"
+
+
+local _M = {
+ version = 0.1,
+ priority = 23000,
+ name = plugin_name,
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function get_addr(conf, ctx)
+ return ctx.var[conf.source]
+end
+
+
+function _M.rewrite(conf, ctx)
+ if not is_apisix_or then
+ core.log.error("need to build APISIX-OpenResty to support setting real
ip")
+ return 501
+ end
+
+ local addr = get_addr(conf, ctx)
+ if not addr then
+ core.log.warn("missing real address")
+ return
+ end
+
+ local ip, port = core.utils.parse_addr(addr)
+ if not ip or (not core.utils.parse_ipv4(ip) and not
core.utils.parse_ipv6(ip)) then
+ core.log.warn("bad address: ", addr)
+ return
+ end
+
+ if str_byte(ip, 1, 1) == str_byte("[") then
+ -- For IPv6, the `set_real_ip` accepts '::1' but not '[::1]'
+ ip = str_sub(ip, 2, #ip - 1)
+ end
+
+ if port ~= nil and (port < 1 or port > 65535) then
+ core.log.warn("bad port: ", port)
+ return
+ end
+
+ core.log.info("set real ip: ", ip, ", port: ", port)
+
+ local ok, err = client.set_real_ip(ip, port)
+ if not ok then
+ core.log.error("failed to set real ip: ", err)
+ return
+ end
+
+ -- flush cached vars in APISIX
+ ctx.var.remote_addr = nil
+ ctx.var.remote_port = nil
+ ctx.var.realip_remote_addr = nil
+ ctx.var.realip_remote_port = nil
+end
+
+
+return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 53930c3..80d0978 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -271,6 +271,7 @@ graphql:
#cmd: ["ls", "-l"]
plugins: # plugin list (sorted by priority)
+ - real-ip # priority: 23000
- client-control # priority: 22000
- ext-plugin-pre-req # priority: 12000
- zipkin # priority: 11011
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index 1e3094c..a7135c2 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -39,6 +39,7 @@
"plugins/redirect",
"plugins/echo",
"plugins/gzip",
+ "plugins/real-ip",
"plugins/server-info",
"plugins/ext-plugin-pre-req",
"plugins/ext-plugin-post-req"
diff --git a/docs/en/latest/plugins/client-control.md
b/docs/en/latest/plugins/client-control.md
index f191323..80e5d04 100644
--- a/docs/en/latest/plugins/client-control.md
+++ b/docs/en/latest/plugins/client-control.md
@@ -34,7 +34,7 @@ title: client-control
The `client-control` plugin dynamically controls the behavior of Nginx to
handle the client request.
-This plugin requires APISIX to run on
[APISIX-OpenResty](../how-to-build.md#step-6-build-openresty-for-apache-apisix).
+**This plugin requires APISIX to run on
[APISIX-OpenResty](../how-to-build.md#step-6-build-openresty-for-apache-apisix).**
## Attributes
diff --git a/docs/en/latest/plugins/gzip.md b/docs/en/latest/plugins/gzip.md
index d947215..a7a6af9 100644
--- a/docs/en/latest/plugins/gzip.md
+++ b/docs/en/latest/plugins/gzip.md
@@ -33,7 +33,7 @@ title: gzip
The `gzip` plugin dynamically set the gzip behavior of Nginx.
-This plugin requires APISIX to run on
[APISIX-OpenResty](../how-to-build.md#step-6-build-openresty-for-apache-apisix).
+**This plugin requires APISIX to run on
[APISIX-OpenResty](../how-to-build.md#step-6-build-openresty-for-apache-apisix).**
## Attributes
diff --git a/docs/en/latest/plugins/client-control.md
b/docs/en/latest/plugins/real-ip.md
similarity index 67%
copy from docs/en/latest/plugins/client-control.md
copy to docs/en/latest/plugins/real-ip.md
index f191323..673d973 100644
--- a/docs/en/latest/plugins/client-control.md
+++ b/docs/en/latest/plugins/real-ip.md
@@ -1,5 +1,5 @@
---
-title: client-control
+title: real-ip
---
<!--
@@ -31,16 +31,19 @@ title: client-control
## Name
-The `client-control` plugin dynamically controls the behavior of Nginx to
-handle the client request.
+The `real-ip` plugin dynamically changes the client's IP and port seen by
APISIX.
-This plugin requires APISIX to run on
[APISIX-OpenResty](../how-to-build.md#step-6-build-openresty-for-apache-apisix).
+It works like Nginx's `ngx_http_realip_module`, but is more flexible.
+
+**This plugin requires APISIX to run on
[APISIX-OpenResty](../how-to-build.md#step-6-build-openresty-for-apache-apisix).**
## Attributes
| Name | Type | Requirement | Default | Valid
| Description
|
| --------- | ------------- | ----------- | ---------- |
------------------------------------------------------------------------ |
---------------------------------------------------------------------------------------------------------------------------------------------------
|
-| max_body_size | integer | optional | | >= 0 |
dynamically set the `client_max_body_size` directive |
+| source | string | required | | Any Nginx variable
like `arg_realip` or `http_x_forwarded_for`| dynamically set the client's IP
and port in APISIX's view, according to the value of variable. If the value
doesn't contain a port, the client's port won't be changed. |
+
+If the remote address comes from `source` is missing or invalid, this plugin
will just let it go and don't change the client address.
## How To Enable
@@ -51,14 +54,20 @@ curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H
'X-API-KEY: edd1c9f03433
{
"uri": "/index.html",
"plugins": {
- "client-control": {
- "max_body_size" : 1
+ "real-ip": {
+ "source": "arg_realip"
+ },
+ "response-rewrite": {
+ "headers": {
+ "remote_addr": "$remote_addr",
+ "remote_port": "$remote_port"
+ }
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
- "39.97.63.215:80": 1
+ "127.0.0.1:1980": 1
}
}
}'
@@ -69,23 +78,16 @@ curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H
'X-API-KEY: edd1c9f03433
Use curl to access:
```shell
-curl -i http://127.0.0.1:9080/index.html -d '123'
-
-HTTP/1.1 413 Request Entity Too Large
+curl 'http://127.0.0.1:9080/index.html?realip=1.2.3.4:9080' -I
...
-<html>
-<head><title>413 Request Entity Too Large</title></head>
-<body>
-<center><h1>413 Request Entity Too Large</h1></center>
-<hr><center>openresty</center>
-</body>
-</html>
+remote-addr: 1.2.3.4
+remote-port: 9080
```
## Disable Plugin
When you want to disable this plugin, it is very simple,
-you can delete the corresponding json configuration in the plugin
configuration,
+you can delete the corresponding JSON configuration in the plugin
configuration,
no need to restart the service, it will take effect immediately:
```shell
@@ -95,7 +97,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H
'X-API-KEY: edd1c9f034335f1
"upstream": {
"type": "roundrobin",
"nodes": {
- "39.97.63.215:80": 1
+ "127.0.0.1:1980": 1
}
}
}'
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 11354e9..40fcd67 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -40,7 +40,7 @@ __DATA__
--- request
GET /apisix/admin/plugins/list
--- response_body_like eval
-qr/\["client-control","ext-plugin-pre-req","zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","ua-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","authz-casbin","wolf-rbac","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","gzip","server-info","traffic-split","r
[...]
+qr/\["real-ip","client-control","ext-plugin-pre-req","zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","ua-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","authz-casbin","wolf-rbac","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","gzip","server-info","traffic
[...]
--- no_error_log
[error]
diff --git a/t/core/config.t b/t/core/config.t
index 2ffd45c..b87fe12 100644
--- a/t/core/config.t
+++ b/t/core/config.t
@@ -38,7 +38,7 @@ __DATA__
GET /t
--- response_body
etcd host: http://127.0.0.1:2379
-first plugin: "client-control"
+first plugin: "real-ip"
diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t
index ea84728..f13e68c 100644
--- a/t/debug/debug-mode.t
+++ b/t/debug/debug-mode.t
@@ -44,6 +44,7 @@ GET /t
--- response_body
done
--- error_log
+loaded plugin and sort by priority: 23000 name: real-ip
loaded plugin and sort by priority: 22000 name: client-control
loaded plugin and sort by priority: 12000 name: ext-plugin-pre-req
loaded plugin and sort by priority: 11011 name: zipkin
diff --git a/t/plugin/real-ip.t b/t/plugin/real-ip.t
new file mode 100644
index 0000000..23badde
--- /dev/null
+++ b/t/plugin/real-ip.t
@@ -0,0 +1,267 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX;
+
+my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
+my $version = eval { `$nginx_binary -V 2>&1` };
+
+if ($version !~ m/\/apisix-nginx-module/) {
+ plan(skip_all => "apisix-nginx-module not installed");
+} else {
+ plan('no_plan');
+}
+
+repeat_each(1);
+log_level('info');
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!$block->request) {
+ $block->set_value("request", "GET /t");
+ }
+
+ if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+ $block->set_value("no_error_log", "[error]");
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: schema check
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "real-ip": {
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.print(body)
+ }
+}
+--- error_code: 400
+--- response_body
+{"error_msg":"failed to check the configuration of plugin real-ip err:
property \"source\" is required"}
+
+
+
+=== TEST 2: sanity
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "real-ip": {
+ "source": "http_xff"
+ },
+ "ip-restriction": {
+ "whitelist": ["1.1.1.1"]
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+}
+--- response_body
+passed
+
+
+
+=== TEST 3: hit
+--- request
+GET /hello
+--- more_headers
+XFF: 1.1.1.1
+
+
+
+=== TEST 4: with port
+--- request
+GET /hello
+--- more_headers
+XFF: 1.1.1.1:80
+
+
+
+=== TEST 5: miss address
+--- request
+GET /hello
+--- error_code: 403
+
+
+
+=== TEST 6: bad address
+--- request
+GET /hello
+--- more_headers
+XFF: 1.1.1.1.1
+--- error_code: 403
+
+
+
+=== TEST 7: bad port
+--- request
+GET /hello
+--- more_headers
+XFF: 1.1.1.1:65536
+--- error_code: 403
+
+
+
+=== TEST 8: ipv6
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "real-ip": {
+ "source": "http_xff"
+ },
+ "ip-restriction": {
+ "whitelist": ["::2"]
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+}
+--- response_body
+passed
+
+
+
+=== TEST 9: hit
+--- request
+GET /hello
+--- more_headers
+XFF: ::2
+
+
+
+=== TEST 10: with port
+--- request
+GET /hello
+--- more_headers
+XFF: [::2]:80
+
+
+
+=== TEST 11: with bracket
+--- request
+GET /hello
+--- more_headers
+XFF: [::2]
+
+
+
+=== TEST 12: check port
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "real-ip": {
+ "source": "http_xff"
+ },
+ "response-rewrite": {
+ "headers": {
+ "remote_port": "$remote_port"
+ }
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+}
+--- response_body
+passed
+
+
+
+=== TEST 13: hit
+--- request
+GET /hello
+--- more_headers
+XFF: 1.1.1.1:7090
+--- response_headers
+remote_port: 7090