Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ip-restriction wasm-go plugin #759

Merged
merged 39 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4675ec0
feat: add ip-restriction wasm-go plugin
Renz7 Jan 6, 2024
0098f26
feat: radix tree store; update deny allow policy
Renz7 Jan 8, 2024
ff7f87c
feat: multi IP source support
Renz7 Jan 23, 2024
0060089
feat: wasm support opa (Open Policy Agent) (#760)
baerwang Jan 30, 2024
66ab4e2
bugfix: "path=/" will cause panic (#809)
rinfx Feb 1, 2024
a1457dc
Enable srds by default (#811)
johnlanni Feb 1, 2024
442acaa
rel: Release verion 1.3.4-rc.2 (#812)
johnlanni Feb 1, 2024
8b40188
Update Makefile.core.mk
johnlanni Feb 1, 2024
931a8db
fix rds cache (#815)
johnlanni Feb 2, 2024
6e53b17
feat: add request-validation plugin (#700)
sjcsjc123 Feb 2, 2024
a6016a5
Implement the Go Wasm plugin: bot-detect (#747)
OnlyPiglet Feb 2, 2024
efefdba
Update Makefile.core.mk
johnlanni Feb 3, 2024
312ac12
fix: hgctl latest version bug (#816)
2456868764 Feb 3, 2024
285dccf
feat: add new param for global option (#813)
sjcsjc123 Feb 4, 2024
6362d72
increase health check timeout (#820)
johnlanni Feb 4, 2024
b2b0459
Update build-image-and-push.yaml
johnlanni Feb 4, 2024
7711ec9
Update build-image-and-push.yaml
johnlanni Feb 4, 2024
d736a21
Update build-image-and-push.yaml
johnlanni Feb 4, 2024
ae46187
Update build-image-and-push.yaml
johnlanni Feb 4, 2024
961279f
Update Makefile.core.mk
johnlanni Feb 4, 2024
2ac31d3
feat: implement custom-response plugin in the golang version (#689)
cr7258 Feb 20, 2024
b0aaced
Increase health check timeout of the first readiness probe from pilot…
johnlanni Feb 20, 2024
61454a2
test: allow specifying HTTP protocol (#822)
spacewander Feb 20, 2024
cb4856d
rel: Release verison 1.3.4 (#828)
johnlanni Feb 20, 2024
7cc3a9a
optimize: add klog for debuging requests with apiserver (#830)
johnlanni Feb 20, 2024
ddb99c1
fix: content-type not work in custom response plugin (#833)
johnlanni Feb 21, 2024
0975d3b
feat: hgctl install profile support resource configuration (#823)
sjcsjc123 Feb 23, 2024
6d1a255
chore: e2e build wasm plugin retry (#838)
baerwang Feb 23, 2024
e663035
feat: custom listening port for gateway pod in helm (#829)
Uncle-Justice Feb 23, 2024
3513265
Support redis call (#756)
rinfx Feb 23, 2024
78ec968
replace proxy-wasm-go-sdk (#842)
rinfx Feb 26, 2024
75d35d1
Update build-and-test-plugin.yaml
johnlanni Feb 26, 2024
0733993
support nil wasmplugin config in ingress-level (#852)
johnlanni Mar 1, 2024
6d0c2af
feat: add e2e test
Renz7 Mar 4, 2024
34e50fb
Merge branch 'main' into ip-restriction
Renz7 Mar 4, 2024
a5345e3
fix: modify e2e test
Renz7 Mar 5, 2024
05414df
Update README.md
johnlanni Mar 5, 2024
6456ad0
Merge branch 'main' into ip-restriction
Renz7 Mar 6, 2024
c297936
Merge branch 'main' into ip-restriction
Renz7 Mar 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions plugins/wasm-go/extensions/ip-restriction/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 功能说明

`ip-restriction `插件可以通过将 IP 地址列入白名单或黑名单来限制对服务或路由的访问.支持对单个 IP 地址、多个 IP 地址和类似
10.10.10.0/24 的 CIDR范围的限制.

# 配置说明

| 配置项 | 类型 | 必填 | 默认值 | 说明 |
|----------------|--------|----|-----------------------------|------------------------------------------|
| ip_source_type | string | 否 | origin-source | 可选值:1. 对端socket ip:`origin-source`; 2. 通过header获取:`header` |
| ip_header_name | string | 否 | x-forwarded-for | 当`ip_source_type`为`header`时,指定自定义IP来源头 |
| allow | array | 否 | [] | 白名单列表 |
| deny | array | 否 | [] | 黑名单列表 |
| status | int | 否 | 403 | 拒绝访问时的 HTTP 状态码 |
| message | string | 否 | Your IP address is blocked. | 拒绝访问时的返回信息 |


```yaml
ip_source_type: origin-source
allow:
- 10.0.0.1
- 192.168.0.0/16
```

```yaml
ip_source_type: header
ip_header_name: x-real-iP
deny:
- 10.0.0.1
- 192.169.0.0/16
```
1 change: 1 addition & 0 deletions plugins/wasm-go/extensions/ip-restriction/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
22 changes: 22 additions & 0 deletions plugins/wasm-go/extensions/ip-restriction/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/ip-restriction

go 1.19

replace github.com/alibaba/higress/plugins/wasm-go => ../..

require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
github.com/tidwall/gjson v1.14.3
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837
)

require (
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/resp v0.1.1 // indirect
)
24 changes: 24 additions & 0 deletions plugins/wasm-go/extensions/ip-restriction/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56 h1:Wi5Tgn8K+jDcBYL+dIMS1+qXYH2r7tpRAyBgqrWfQtw=
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56/go.mod h1:8BhOLuqtSuT5NZtZMwfvEibi09RO3u79uqfHZzfDTR4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837 h1:DjHnADS2r2zynZ3WkCFAQ+PNYngMSNceRROi0pO6c3M=
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837/go.mod h1:9vp0bxqozzQwcjBwenEXfKVq8+mYbwHkQ1NF9Ap0DMw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
157 changes: 157 additions & 0 deletions plugins/wasm-go/extensions/ip-restriction/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package main

import (
"encoding/json"
"fmt"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
"github.com/zmap/go-iptree/iptree"
"net"
"strings"
)

const (
DefaultRealIpHeader string = "X-Forwarded-For"
DefaultDenyStatus uint32 = 403
DefaultDenyMessage string = "Your IP address is blocked."
)
const (
OriginSourceType = "origin-source"
HeaderSourceType = "header"
)

type RestrictionConfig struct {
IPSourceType string `json:"ip_source_type"` //IP来源类型
IPHeaderName string `json:"ip_header_name"` //真实IP头
Allow *iptree.IPTree `json:"allow"` //允许的IP
Deny *iptree.IPTree `json:"deny"` //拒绝的IP
Status uint32 `json:"status"` //被拒绝时返回的状态码
Message string `json:"message"` //被拒绝时返回的消息
}

func main() {
wrapper.SetCtx(
"ip-restriction",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders))
}

func parseConfig(json gjson.Result, config *RestrictionConfig, log wrapper.Log) error {
sourceType := json.Get("ip_source_type")
if sourceType.Exists() && sourceType.String() != "" {
switch sourceType.String() {
case HeaderSourceType:
config.IPSourceType = HeaderSourceType
case OriginSourceType:
default:
config.IPSourceType = OriginSourceType
}
} else {
config.IPSourceType = OriginSourceType
}

header := json.Get("ip_header_name")
if header.Exists() && header.String() != "" {
config.IPHeaderName = header.String()
} else {
config.IPHeaderName = DefaultRealIpHeader
}
status := json.Get("status")
if status.Exists() && status.Uint() > 1 {
config.Status = uint32(header.Uint())
} else {
config.Status = DefaultDenyStatus
}
message := json.Get("message")
if message.Exists() && message.String() != "" {
config.Message = message.String()
} else {
config.Message = DefaultDenyMessage
}
allowNets, err := parseIPNets(json.Get("allow").Array())
Renz7 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
log.Error(err.Error())
return err
}
denyNets, err := parseIPNets(json.Get("deny").Array())
if err != nil {
log.Error(err.Error())
return err
}
if allowNets != nil && denyNets != nil {
Renz7 marked this conversation as resolved.
Show resolved Hide resolved
log.Warn("allow and deny cannot be set at the same time")
return fmt.Errorf("allow and deny cannot be set at the same time")
}
if allowNets == nil && denyNets == nil {
log.Warn("allow and deny cannot be empty at the same time")
return fmt.Errorf("allow and deny cannot be empty at the same time")
}
config.Allow = allowNets
config.Deny = denyNets
return nil
}

func getDownStreamIp(config RestrictionConfig) (net.IP, error) {
var (
s string
err error
)

if config.IPSourceType == HeaderSourceType {
s, err = proxywasm.GetHttpRequestHeader(config.IPHeaderName)
if err == nil {
s = strings.Split(strings.Trim(s, " "), ",")[0]
}
} else {
var bs []byte
bs, err = proxywasm.GetProperty([]string{"source", "address"})
s = string(bs)
}
if err != nil {
return nil, err
}
ip := parseIP(s)
realIP := net.ParseIP(ip)
if realIP == nil {
return nil, fmt.Errorf("invalid ip[%s]", ip)
}
return realIP, nil
}

func onHttpRequestHeaders(context wrapper.HttpContext, config RestrictionConfig, log wrapper.Log) types.Action {
realIp, err := getDownStreamIp(config)
if err != nil {
return deniedUnauthorized(config)
}
allow := config.Allow
deny := config.Deny
if allow != nil {
if realIp == nil {
log.Error("realIp is nil, blocked")
return deniedUnauthorized(config)
}
if _, found, _ := allow.Get(realIp); !found {
return deniedUnauthorized(config)
}
}
if deny != nil {
if realIp == nil {
log.Error("realIp is nil, continue")
return types.ActionContinue
}
if _, found, _ := deny.Get(realIp); found {
return deniedUnauthorized(config)
}
}
return types.ActionContinue
}

func deniedUnauthorized(config RestrictionConfig) types.Action {
body, _ := json.Marshal(map[string]string{
"message": config.Message,
})
_ = proxywasm.SendHttpResponse(config.Status, nil, body, -1)
return types.ActionContinue
}
37 changes: 37 additions & 0 deletions plugins/wasm-go/extensions/ip-restriction/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"fmt"
"github.com/tidwall/gjson"
"github.com/zmap/go-iptree/iptree"
"strings"
)

// parseIPNets 解析Ip段配置
func parseIPNets(array []gjson.Result) (*iptree.IPTree, error) {
if len(array) == 0 {
return nil, nil
} else {
tree := iptree.New()
for _, result := range array {
err := tree.AddByString(result.String(), 0)
if err != nil {
return nil, fmt.Errorf("invalid IP[%s]", result.String())
}
}
return tree, nil
}
}

// parseIP 解析IP
func parseIP(source string) string {
if strings.Contains(source, ".") {
// parse ipv4
return strings.Split(source, ":")[0]
}
//parse ipv6
if strings.Contains(source, "]") {
return strings.Split(source, "]")[0][1:]
}
return source
}
106 changes: 106 additions & 0 deletions plugins/wasm-go/extensions/ip-restriction/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package main

import (
"github.com/tidwall/gjson"
"testing"
)

func Test_parseIPNets(t *testing.T) {
type args struct {
array []gjson.Result
}
tests := []struct {
name string
args args
wantVal bool
wantErr bool
}{
{
name: "",
args: args{
array: gjson.Parse(`["127.0.0.1/30","10.0.0.1"]`).Array(),
},
wantVal: true,
wantErr: false,
},
{
name: "",
args: args{
array: gjson.Parse(``).Array(),
},
wantVal: false,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseIPNets(tt.args.array)
if (err != nil) != tt.wantErr {
t.Errorf("parseIPNets() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantVal && got == nil {
return
}
if _, found, _ := got.GetByString("10.0.0.1"); found != tt.wantVal {
t.Errorf("parseIPNets() got = %v, want %v", found, tt.wantVal)
return
}
})
}
}

func Test_parseIP(t *testing.T) {
type args struct {
source string
}
tests := []struct {
name string
args args
want string
}{
// TODO: Add test cases.
{
name: "case 1",
args: args{
"127.0.0.1",
},
want: "127.0.0.1",
},
{
name: "case 2",
args: args{
"127.0.0.1:12",
},
want: "127.0.0.1",
},
{
name: "case 3",
args: args{
"fe80::14d5:8aff:fed9:2114",
},
want: "fe80::14d5:8aff:fed9:2114",
},
{
name: "case 4",
args: args{
"[fe80::14d5:8aff:fed9:2114]:123",
},
want: "fe80::14d5:8aff:fed9:2114",
},
{
name: "case 5",
args: args{
"127.0.0.1:12,[fe80::14d5:8aff:fed9:2114]:123",
},
want: "127.0.0.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := parseIP(tt.args.source); got != tt.want {
t.Errorf("parseIP() = %v, want %v", got, tt.want)
}
})
}
}
Loading
Loading