Compare commits

..

18 Commits

Author SHA1 Message Date
澄潭
5f32e159e5 Jwt policy compat (#266) 2023-04-03 16:14:42 +08:00
澄潭
7fd3f43c0d fix bug of port parsing in destination annotation (#262) 2023-03-30 19:24:55 +08:00
澄潭
c96ede21a5 refactor registry & add direct service discovery through staticIP or DNS (#261) 2023-03-29 18:45:57 +08:00
澄潭
87366aab49 Create SECURITY.md 2023-03-29 10:00:31 +08:00
澄潭
1a711bd267 Update issue templates 2023-03-29 09:56:54 +08:00
澄潭
d0d03e0e36 Delete BUG_REPORT.md 2023-03-29 09:55:35 +08:00
澄潭
755bcc2d58 Update issue templates 2023-03-29 09:55:13 +08:00
澄潭
42fddb6115 Update BUG_REPORT.md 2023-03-29 09:49:46 +08:00
onlypiglet
09e563cf9c doc: add basic_auth plugin english readme (#221) 2023-03-25 21:43:20 +08:00
澄潭
ef6912e466 Wasmplugin support disable config (#255) 2023-03-25 15:51:07 +08:00
casun18
07ce165661 doc: fix key_auth plugin readme (#256) 2023-03-25 15:50:20 +08:00
Qianglin Li
3844017bb9 add e2e test for canary weight (#245)
Signed-off-by: charlie <qianglin98@qq.com>
Co-authored-by: Xunzhuo <bitliu@tencent.com>
2023-03-24 17:46:20 +08:00
Yang
406b890a2a Add header control (#254) 2023-03-24 17:45:27 +08:00
澄潭
af31d455ed fix zk registry (#253) 2023-03-24 16:00:41 +08:00
澄潭
4a9e5aafd0 Add Charlie17Li to CODEOWNERS 2023-03-21 17:33:20 +08:00
Kent Dong
9dfabee26a feat: Support Wasm plugin OCI images with more than 2 layers (#244) 2023-03-20 13:00:33 +08:00
澄潭
19acbb4647 Update README_EN.md 2023-03-18 17:10:46 +08:00
澄潭
96ada21174 Update README.md 2023-03-18 17:05:15 +08:00
43 changed files with 1498 additions and 503 deletions

View File

@@ -1,6 +1,9 @@
---
name: Feature Request
about: Suggest an idea for Higress
title: ''
labels: ''
assignees: ''
---
@@ -13,4 +16,4 @@ A clear and concise description of what you want to happen. You can explain more
## Other related information
Add any other context or screenshots about the feature request here.
Add any other context or screenshots about the feature request here.

View File

@@ -1,3 +1,16 @@
---
name: Non-{crash,security} bug
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**If you are reporting *any* crash or *any* potential security issue, *do not*
open an issue in this repo. Please report the issue via [ASRC](https://security.alibaba.com/)(Alibaba Security Response Center) where the issue will be triaged appropriately.**
---
name: Bug Report
about: If you would like to report an issue to Higress, please use this template.
@@ -35,4 +48,4 @@ Just paste your stack trace here!
- Higress version:
- OS :
- Others:
- Others:

View File

@@ -1,7 +1,7 @@
/api @johnlanni
/envoy @gengleilei @johnlanni @Lynskylate
/istio @SpecialYang @johnlanni
/pkg @SpecialYang @johnlanni
/pkg @SpecialYang @johnlanni @Charlie17Li
/plugins @johnlanni
/registry @NameHaibinZhang @johnlanni
/test @Xunzhuo

View File

@@ -193,7 +193,7 @@ kube-load-image: $(tools/kind) ## Install the EG image to a kind cluster using t
run-ingress-e2e-test:
@echo -e "\n\033[36mRunning higress conformance tests...\033[0m"
@echo -e "\n\033[36mWaiting higress-controller to be ready...\033[0m\n"
kubectl wait --timeout=5m -n higress-system deployment/higress-controller --for=condition=Available
kubectl wait --timeout=10m -n higress-system deployment/higress-controller --for=condition=Available
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
kubectl wait --timeout=5m -n higress-system deployment/higress-gateway --for=condition=Available
kubectl wait --timeout=10m -n higress-system deployment/higress-gateway --for=condition=Available
go test -v -tags conformance ./test/ingress/e2e_test.go --ingress-class=higress --debug=true

160
README.md
View File

@@ -26,7 +26,7 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
- [**使用场景**](#使用场景)
- [**核心优势**](#核心优势)
- [**Quick Start**](#quick-start)
- [**Quick Start**](https://higress.io/zh-cn/docs/user/quickstart)
- [**社区**](#社区)
## 使用场景
@@ -73,164 +73,6 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
插件支持热更新,变更插件逻辑和配置都对流量无损。
## Quick Start
- [**本地环境**](#本地环境)
- [**生产环境**](#生产环境)
### 本地环境
#### 第一步、 安装 kubectl & kind
**MacOS**
```bash
curl -Lo ./kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl
# for Intel Macs
[ $(uname -m) = x86_64 ]&& curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-darwin-amd64
# for M1 / ARM Macs
[ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-darwin-arm64
chmod +x ./kind ./kubectl
mv ./kind ./kubectl /some-dir-in-your-PATH/
```
**Windows 中使用 PowerShell:**
```bash
curl.exe -Lo kubectl.exe https://storage.googleapis.com/kubernetes-release/release/$(curl.exe -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/windows/amd64/kubectl.exe
curl.exe -Lo kind-windows-amd64.exe https://kind.sigs.k8s.io/dl/v0.17.0/kind-windows-amd64
Move-Item .\kind-windows-amd64.exe c:\some-dir-in-your-PATH\kind.exe
Move-Item .\kubectl.exe c:\some-dir-in-your-PATH\kubectl.exe
```
**Linux:**
```bash
curl -Lo ./kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-linux-amd64
chmod +x ./kind ./kubectl
sudo mv ./kind ./kubectl /usr/local/bin/kind
```
#### 第二步、 创建并启用 kind
首先创建一个集群配置文件: `cluster.conf`
```yaml
# cluster.conf
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
```
Mac & Linux 系统执行:
```bash
kind create cluster --name higress --config=cluster.conf
kubectl config use-context kind-higress
```
Windows 系统执行:
```bash
kind.exe create cluster --name higress --config=cluster.conf
kubectl.exe config use-context kind-higress
```
#### 第三步、 安装 higress
```bash
helm repo add higress.io https://higress.io/helm-charts
helm install higress higress.io/higress -n higress-system --set global.kind=true --create-namespace --render-subchart-notes
```
helm版本需升级至**v3.8.0**及以上
#### 第四步、 创建 Ingress 资源并测试
```bash
kubectl apply -f https://higress.io/samples/quickstart.yaml
```
测试 Ingress 生效:
```bash
# should output "foo"
curl localhost/foo
# should output "bar"
curl localhost/bar
```
#### 卸载资源
```bash
kubectl delete -f https://higress.io/samples/quickstart.yaml
helm uninstall higress -n higress-system
kubectl delete ns higress-system
```
### 生产环境
#### 第一步、 安装 higress
```bash
helm repo add higress.io https://higress.io/helm-charts
helm install higress higress.io/higress -n higress-system --create-namespace --render-subchart-notes
```
#### 第二步、 创建 Ingress 资源并测试
假设在 default 命名空间下已经部署了一个 test service服务端口为 80 ,则创建下面这个 K8s Ingress
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-example
spec:
ingressClassName: higress
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: test
port:
number: 80
```
测试能访问到该服务:
```bash
curl "$(k get svc -n higress-system higress-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"/foo -H 'host: foo.bar.com'
```
#### 卸载资源
```bash
helm uninstall higress -n higress-system
kubectl delete ns higress-system
```
## 社区

View File

@@ -21,7 +21,7 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
- [**Use Cases**](#use-cases)
- [**Higress Features**](#higress-features)
- [**Quick Start**](#quick-start)
- [**Quick Start**](https://higress.io/en-us/docs/user/quickstart)
- [**Thanks**](#thanks)
## Use Cases
@@ -44,162 +44,25 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
## Higress Features
TODO
- **Easy to use**
Provide one-stop gateway solutions for traffic scheduling, service management, and security protection, support Console, K8s Ingress, and Gateway API configuration methods, and also support HTTP to Dubbo protocol conversion, and easily complete protocol mapping configuration.
## Quick Start
- **Easy to expand**
- [**Local Environment**](#local-environment)
- [**Production Environment**](#production-environment)
Provides Wasm, Lua, and out-of-process plug-in extension mechanisms, so that multi-language plug-in writing is no longer an obstacle. The granularity of plug-in effectiveness supports not only the global level, domain name level, but also fine-grained routing level
- **Dynamic hot update**
Get rid of the traffic jitter caused by reload at the bottom, the configuration change takes effect in milliseconds and the business is not affected, the Wasm plug-in is hot updated and the traffic is not damaged
- **Smooth upgrade**
### Local Environment
Compatible with 80%+ usage scenarios of Nginx Ingress Annotation, and provides more feature-rich annotations, easy to handle Nginx Ingress migration in one step
- **Security**
#### step 1. install kubectl & kind
**On MacOS:**
```bash
curl -Lo ./kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl
# for Intel Macs
[ $(uname -m) = x86_64 ]&& curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-darwin-amd64
# for M1 / ARM Macs
[ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-darwin-arm64
chmod +x ./kind ./kubectl
mv ./kind ./kubectl /some-dir-in-your-PATH/
```
**On Windows in PowerShell:**
```bash
curl.exe -Lo kubectl.exe https://storage.googleapis.com/kubernetes-release/release/$(curl.exe -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/windows/amd64/kubectl.exe
curl.exe -Lo kind-windows-amd64.exe https://kind.sigs.k8s.io/dl/v0.17.0/kind-windows-amd64
Move-Item .\kind-windows-amd64.exe c:\some-dir-in-your-PATH\kind.exe
Move-Item .\kubectl.exe c:\some-dir-in-your-PATH\kubectl.exe
```
**On Linux:**
```bash
curl -Lo ./kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-linux-amd64
chmod +x ./kind ./kubectl
sudo mv ./kind ./kubectl /usr/local/bin/kind
```
#### step 2. create kind cluster
create a cluster config file: `cluster.conf`
```yaml
# cluster.conf
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
```
Mac & Linux:
```bash
kind create cluster --name higress --config=cluster.conf
kubectl config use-context kind-higress
```
Windows:
```bash
kind.exe create cluster --name higress --config=cluster.conf
kubectl.exe config use-context kind-higress
```
#### step 3. install higress
```bash
helm repo add higress.io https://higress.io/helm-charts
helm install higress higress.io/higress -n higress-system --create-namespace --set global.kind=true
```
Note: The helm version needs to be upgraded to **v3.8.0** and above
#### step 4. create the ingress and test it
```bash
kubectl apply -f https://higress.io/samples/quickstart.yaml
```
Now verify that the ingress works
```bash
# should output "foo"
curl localhost/foo
# should output "bar"
curl localhost/bar
```
#### Clean-Up
```bash
kubectl delete -f https://higress.io/samples/quickstart.yaml
helm uninstall higress -n higress-system
kubectl delete ns higress-system
```
### Production Environment
#### step 1. install higress
```bash
helm repo add higress.io https://higress.io/helm-charts
helm install higress higress.io/higress -n higress-system --create-namespace
```
#### step 2. create the ingress and test it
for example there is a service `test` in default namespace.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-example
spec:
ingressClassName: higress
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: test
port:
number: 80
```
```bash
curl "$(k get svc -n higress-system higress-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"/foo -H 'host: foo.bar.com'
```
#### Clean-Up
```bash
helm uninstall higress -n higress-system
kubectl delete ns higress-system
```
Provides JWT, OIDC, custom authentication and authentication, deeply integrates open source web application firewall.
### Thanks

14
SECURITY.md Normal file
View File

@@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
| < 1.0.0 | :x: |
## Reporting a Vulnerability
Please report any security issue or Higress crash report to [ASRC](https://security.alibaba.com/)(Alibaba Security Response Center) where the issue will be triaged appropriately.
Thank you in advance for helping to keep Higress secure.

View File

@@ -1 +1 @@
v0.7.0
v0.7.1

View File

@@ -170,10 +170,12 @@ type WasmPlugin struct {
// Extended by Higress, the default configuration takes effect globally
DefaultConfig *types.Struct `protobuf:"bytes,101,opt,name=default_config,json=defaultConfig,proto3" json:"default_config,omitempty"`
// Extended by Higress, matching rules take effect
MatchRules []*MatchRule `protobuf:"bytes,102,rep,name=match_rules,json=matchRules,proto3" json:"match_rules,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
MatchRules []*MatchRule `protobuf:"bytes,102,rep,name=match_rules,json=matchRules,proto3" json:"match_rules,omitempty"`
// diable the default config
DefaultConfigDisable bool `protobuf:"varint,103,opt,name=default_config_disable,json=defaultConfigDisable,proto3" json:"default_config_disable,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *WasmPlugin) Reset() { *m = WasmPlugin{} }
@@ -286,11 +288,19 @@ func (m *WasmPlugin) GetMatchRules() []*MatchRule {
return nil
}
func (m *WasmPlugin) GetDefaultConfigDisable() bool {
if m != nil {
return m.DefaultConfigDisable
}
return false
}
// Extended by Higress
type MatchRule struct {
Ingress []string `protobuf:"bytes,1,rep,name=ingress,proto3" json:"ingress,omitempty"`
Domain []string `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
Config *types.Struct `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"`
ConfigDisable bool `protobuf:"varint,4,opt,name=config_disable,json=configDisable,proto3" json:"config_disable,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -350,6 +360,13 @@ func (m *MatchRule) GetConfig() *types.Struct {
return nil
}
func (m *MatchRule) GetConfigDisable() bool {
if m != nil {
return m.ConfigDisable
}
return false
}
func init() {
proto.RegisterEnum("higress.extensions.v1alpha1.PluginPhase", PluginPhase_name, PluginPhase_value)
proto.RegisterEnum("higress.extensions.v1alpha1.PullPolicy", PullPolicy_name, PullPolicy_value)
@@ -360,43 +377,46 @@ func init() {
func init() { proto.RegisterFile("extensions/v1alpha1/wasm.proto", fileDescriptor_4d60b240916c4e18) }
var fileDescriptor_4d60b240916c4e18 = []byte{
// 576 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0xdd, 0x6e, 0xd3, 0x30,
0x14, 0xc7, 0x97, 0x76, 0xeb, 0xd6, 0xd3, 0x6d, 0x64, 0x96, 0x18, 0xd6, 0x86, 0x4a, 0xb5, 0x0b,
0x28, 0xbb, 0x48, 0xb4, 0x02, 0xe3, 0x06, 0x4d, 0x74, 0xa3, 0xb0, 0x0a, 0x28, 0x51, 0xb2, 0x81,
0xd8, 0x4d, 0xe5, 0x66, 0x6e, 0x6a, 0xe1, 0xc4, 0x91, 0xed, 0x6c, 0xf4, 0xf9, 0xb8, 0xe1, 0x92,
0x47, 0x40, 0x7b, 0x12, 0x54, 0x27, 0xd9, 0x07, 0xa0, 0xde, 0x9d, 0x8f, 0xdf, 0x39, 0xf9, 0xff,
0x8f, 0x1c, 0x68, 0xd2, 0xef, 0x9a, 0x26, 0x8a, 0x89, 0x44, 0xb9, 0x17, 0x7b, 0x84, 0xa7, 0x13,
0xb2, 0xe7, 0x5e, 0x12, 0x15, 0x3b, 0xa9, 0x14, 0x5a, 0xa0, 0xed, 0x09, 0x8b, 0x24, 0x55, 0xca,
0xb9, 0xe1, 0x9c, 0x92, 0xdb, 0x6a, 0x46, 0x42, 0x44, 0x9c, 0xba, 0x06, 0x1d, 0x65, 0x63, 0xf7,
0x52, 0x92, 0x34, 0xa5, 0x52, 0xe5, 0xc3, 0x5b, 0x0f, 0xff, 0xee, 0x2b, 0x2d, 0xb3, 0x50, 0xe7,
0xdd, 0x9d, 0x1f, 0x8b, 0x00, 0x5f, 0x88, 0x8a, 0x3d, 0x9e, 0x45, 0x2c, 0x41, 0x36, 0x54, 0x33,
0xc9, 0x71, 0xa5, 0x65, 0xb5, 0xeb, 0xfe, 0x2c, 0x44, 0x9b, 0x50, 0x53, 0x13, 0xd2, 0x79, 0xb1,
0x8f, 0xab, 0xa6, 0x58, 0x64, 0x28, 0x80, 0x0d, 0x16, 0x93, 0x88, 0x0e, 0xd3, 0x8c, 0xf3, 0x61,
0x2a, 0x38, 0x0b, 0xa7, 0x78, 0xb1, 0x65, 0xb5, 0xd7, 0x3b, 0x4f, 0x9c, 0x39, 0x7a, 0x1d, 0x2f,
0xe3, 0xdc, 0x33, 0xb8, 0x7f, 0xcf, 0x6c, 0xb8, 0x29, 0xa0, 0xdd, 0x3b, 0x4b, 0x15, 0x0d, 0x25,
0xd5, 0x78, 0xc9, 0x7c, 0xf7, 0x86, 0x0d, 0x4c, 0x19, 0x3d, 0x05, 0xfb, 0x82, 0x4a, 0x36, 0x66,
0x21, 0xd1, 0x4c, 0x24, 0xc3, 0x6f, 0x74, 0x8a, 0x6b, 0x39, 0x7a, 0xbb, 0xfe, 0x9e, 0x4e, 0xd1,
0x2b, 0x58, 0x4b, 0x8d, 0xbf, 0x61, 0x28, 0x92, 0x31, 0x8b, 0xf0, 0x72, 0xcb, 0x6a, 0x37, 0x3a,
0x0f, 0x9c, 0xfc, 0x34, 0x4e, 0x79, 0x1a, 0x27, 0x30, 0xa7, 0xf1, 0x57, 0x73, 0xfa, 0xc8, 0xc0,
0xe8, 0x11, 0x34, 0x8a, 0xe9, 0x84, 0xc4, 0x14, 0xaf, 0x98, 0x6f, 0x40, 0x5e, 0x1a, 0x90, 0x98,
0xa2, 0x03, 0x58, 0x4a, 0x27, 0x44, 0x51, 0x5c, 0x37, 0xf6, 0xdb, 0xf3, 0xed, 0x9b, 0x39, 0x6f,
0xc6, 0xfb, 0xf9, 0x18, 0x7a, 0x09, 0x2b, 0xa9, 0x64, 0x42, 0x32, 0x3d, 0xc5, 0x60, 0x94, 0x6d,
0xff, 0xa3, 0xac, 0x9f, 0xe8, 0xfd, 0xe7, 0x9f, 0x09, 0xcf, 0xa8, 0x7f, 0x0d, 0xa3, 0x03, 0x58,
0x3f, 0xa7, 0x63, 0x92, 0x71, 0x5d, 0x1a, 0xa3, 0xf3, 0x8d, 0xad, 0x15, 0x78, 0xe1, 0xec, 0x1d,
0x34, 0x62, 0xa2, 0xc3, 0xc9, 0x50, 0x66, 0x9c, 0x2a, 0x3c, 0x6e, 0x55, 0xdb, 0x8d, 0xce, 0xe3,
0xb9, 0xf2, 0x3f, 0xce, 0x78, 0x3f, 0xe3, 0xd4, 0x87, 0xb8, 0x0c, 0xd5, 0x4e, 0x02, 0xf5, 0xeb,
0x06, 0xc2, 0xb0, 0xcc, 0x12, 0xb3, 0x01, 0x5b, 0xad, 0x6a, 0xbb, 0xee, 0x97, 0xe9, 0xec, 0x2d,
0x9d, 0x8b, 0x98, 0xb0, 0x04, 0x57, 0x4c, 0xa3, 0xc8, 0x90, 0x0b, 0xb5, 0x42, 0x7f, 0x75, 0xbe,
0xfe, 0x02, 0xdb, 0xed, 0x41, 0xe3, 0xd6, 0x1d, 0xd1, 0x7d, 0xd8, 0x38, 0x1d, 0x04, 0x5e, 0xef,
0xa8, 0xff, 0xb6, 0xdf, 0x7b, 0x33, 0xf4, 0x8e, 0xbb, 0x41, 0xcf, 0x5e, 0x40, 0x75, 0x58, 0xea,
0x9e, 0x9e, 0x1c, 0x0f, 0x6c, 0xab, 0x0c, 0xcf, 0xec, 0xca, 0x2c, 0x0c, 0x4e, 0xba, 0x27, 0x81,
0x5d, 0xdd, 0x3d, 0x04, 0xb8, 0xf5, 0xf8, 0x36, 0x01, 0xdd, 0xd9, 0xf2, 0xe9, 0x43, 0xff, 0xe8,
0xab, 0xbd, 0x80, 0x6c, 0x58, 0xed, 0x8f, 0x07, 0x42, 0x7b, 0x92, 0x2a, 0x9a, 0x68, 0xdb, 0x42,
0x00, 0xb5, 0x2e, 0xbf, 0x24, 0x53, 0x65, 0x57, 0x0e, 0x5f, 0xff, 0xbc, 0x6a, 0x5a, 0xbf, 0xae,
0x9a, 0xd6, 0xef, 0xab, 0xa6, 0x75, 0xd6, 0x89, 0x98, 0x9e, 0x64, 0x23, 0x27, 0x14, 0xb1, 0x4b,
0x38, 0x1b, 0x91, 0x11, 0x71, 0x8b, 0x73, 0xba, 0x24, 0x65, 0xee, 0x7f, 0x7e, 0xf4, 0x51, 0xcd,
0xb8, 0x7c, 0xf6, 0x27, 0x00, 0x00, 0xff, 0xff, 0xb2, 0xe0, 0x3d, 0x06, 0x06, 0x04, 0x00, 0x00,
// 617 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x94, 0xdf, 0x4e, 0x13, 0x41,
0x14, 0xc6, 0xd9, 0x16, 0x0a, 0x3d, 0x05, 0x5c, 0x26, 0x8a, 0x13, 0x30, 0xb5, 0x21, 0x51, 0x57,
0x2e, 0x76, 0x43, 0x45, 0xbc, 0x31, 0xc4, 0x02, 0x55, 0x1a, 0xb5, 0x6e, 0x76, 0x41, 0x23, 0x37,
0x9b, 0xe9, 0x32, 0xdd, 0x4e, 0x9c, 0xfd, 0x93, 0x9d, 0x59, 0xb0, 0x0f, 0xe2, 0x3b, 0x79, 0xe9,
0x23, 0x18, 0xde, 0xc2, 0x3b, 0xd3, 0xd9, 0x2d, 0x6d, 0xd1, 0xf4, 0x6e, 0xe6, 0x9c, 0xdf, 0x39,
0xe7, 0xfb, 0xce, 0x4e, 0x16, 0xea, 0xf4, 0xbb, 0xa4, 0x91, 0x60, 0x71, 0x24, 0xac, 0xab, 0x3d,
0xc2, 0x93, 0x01, 0xd9, 0xb3, 0xae, 0x89, 0x08, 0xcd, 0x24, 0x8d, 0x65, 0x8c, 0xb6, 0x07, 0x2c,
0x48, 0xa9, 0x10, 0xe6, 0x84, 0x33, 0xc7, 0xdc, 0x56, 0x3d, 0x88, 0xe3, 0x80, 0x53, 0x4b, 0xa1,
0xbd, 0xac, 0x6f, 0x5d, 0xa7, 0x24, 0x49, 0x68, 0x2a, 0xf2, 0xe2, 0xad, 0x47, 0x77, 0xf3, 0x42,
0xa6, 0x99, 0x2f, 0xf3, 0xec, 0xce, 0x9f, 0x45, 0x80, 0x2f, 0x44, 0x84, 0x36, 0xcf, 0x02, 0x16,
0x21, 0x1d, 0xca, 0x59, 0xca, 0x71, 0xa9, 0xa1, 0x19, 0x55, 0x67, 0x74, 0x44, 0x9b, 0x50, 0x11,
0x03, 0xd2, 0x7c, 0x79, 0x80, 0xcb, 0x2a, 0x58, 0xdc, 0x90, 0x0b, 0x1b, 0x2c, 0x24, 0x01, 0xf5,
0x92, 0x8c, 0x73, 0x2f, 0x89, 0x39, 0xf3, 0x87, 0x78, 0xb1, 0xa1, 0x19, 0xeb, 0xcd, 0x67, 0xe6,
0x1c, 0xbd, 0xa6, 0x9d, 0x71, 0x6e, 0x2b, 0xdc, 0xb9, 0xa7, 0x3a, 0x4c, 0x02, 0x68, 0x77, 0xa6,
0xa9, 0xa0, 0x7e, 0x4a, 0x25, 0x5e, 0x52, 0x73, 0x27, 0xac, 0xab, 0xc2, 0xe8, 0x39, 0xe8, 0x57,
0x34, 0x65, 0x7d, 0xe6, 0x13, 0xc9, 0xe2, 0xc8, 0xfb, 0x46, 0x87, 0xb8, 0x92, 0xa3, 0xd3, 0xf1,
0xf7, 0x74, 0x88, 0x5e, 0xc3, 0x5a, 0xa2, 0xfc, 0x79, 0x7e, 0x1c, 0xf5, 0x59, 0x80, 0x97, 0x1b,
0x9a, 0x51, 0x6b, 0x3e, 0x34, 0xf3, 0xd5, 0x98, 0xe3, 0xd5, 0x98, 0xae, 0x5a, 0x8d, 0xb3, 0x9a,
0xd3, 0xc7, 0x0a, 0x46, 0x8f, 0xa1, 0x56, 0x54, 0x47, 0x24, 0xa4, 0x78, 0x45, 0xcd, 0x80, 0x3c,
0xd4, 0x25, 0x21, 0x45, 0x87, 0xb0, 0x94, 0x0c, 0x88, 0xa0, 0xb8, 0xaa, 0xec, 0x1b, 0xf3, 0xed,
0xab, 0x3a, 0x7b, 0xc4, 0x3b, 0x79, 0x19, 0x7a, 0x05, 0x2b, 0x49, 0xca, 0xe2, 0x94, 0xc9, 0x21,
0x06, 0xa5, 0x6c, 0xfb, 0x1f, 0x65, 0x9d, 0x48, 0x1e, 0xec, 0x7f, 0x26, 0x3c, 0xa3, 0xce, 0x2d,
0x8c, 0x0e, 0x61, 0xfd, 0x92, 0xf6, 0x49, 0xc6, 0xe5, 0xd8, 0x18, 0x9d, 0x6f, 0x6c, 0xad, 0xc0,
0x0b, 0x67, 0xef, 0xa0, 0x16, 0x12, 0xe9, 0x0f, 0xbc, 0x34, 0xe3, 0x54, 0xe0, 0x7e, 0xa3, 0x6c,
0xd4, 0x9a, 0x4f, 0xe7, 0xca, 0xff, 0x38, 0xe2, 0x9d, 0x8c, 0x53, 0x07, 0xc2, 0xf1, 0x51, 0xa0,
0x7d, 0xd8, 0x9c, 0x15, 0xe2, 0x5d, 0x32, 0x41, 0x7a, 0x9c, 0xe2, 0xa0, 0xa1, 0x19, 0x2b, 0xce,
0xfd, 0x99, 0xb9, 0x27, 0x79, 0x6e, 0xe7, 0x87, 0x06, 0xd5, 0xdb, 0x7e, 0x08, 0xc3, 0x32, 0x8b,
0xd4, 0x60, 0xac, 0x35, 0xca, 0x46, 0xd5, 0x19, 0x5f, 0x47, 0x4f, 0xf0, 0x32, 0x0e, 0x09, 0x8b,
0x70, 0x49, 0x25, 0x8a, 0x1b, 0xb2, 0xa0, 0x52, 0xd8, 0x2e, 0xcf, 0xb7, 0x5d, 0x60, 0xe8, 0x09,
0xac, 0xdf, 0x91, 0xb7, 0xa8, 0xe4, 0xad, 0xf9, 0xd3, 0xba, 0x76, 0xdb, 0x50, 0x9b, 0xfa, 0x4a,
0xe8, 0x01, 0x6c, 0x9c, 0x77, 0x5d, 0xbb, 0x7d, 0xdc, 0x79, 0xdb, 0x69, 0x9f, 0x78, 0xf6, 0x69,
0xcb, 0x6d, 0xeb, 0x0b, 0xa8, 0x0a, 0x4b, 0xad, 0xf3, 0xb3, 0xd3, 0xae, 0xae, 0x8d, 0x8f, 0x17,
0x7a, 0x69, 0x74, 0x74, 0xcf, 0x5a, 0x67, 0xae, 0x5e, 0xde, 0x3d, 0x02, 0x98, 0x7a, 0xda, 0x9b,
0x80, 0x66, 0xba, 0x7c, 0xfa, 0xd0, 0x39, 0xfe, 0xaa, 0x2f, 0x20, 0x1d, 0x56, 0x3b, 0xfd, 0x6e,
0x2c, 0xed, 0x94, 0x0a, 0x1a, 0x49, 0x5d, 0x43, 0x00, 0x95, 0x16, 0xbf, 0x26, 0x43, 0xa1, 0x97,
0x8e, 0xde, 0xfc, 0xbc, 0xa9, 0x6b, 0xbf, 0x6e, 0xea, 0xda, 0xef, 0x9b, 0xba, 0x76, 0xd1, 0x0c,
0x98, 0x1c, 0x64, 0x3d, 0xd3, 0x8f, 0x43, 0x8b, 0x70, 0xd6, 0x23, 0x3d, 0x62, 0x15, 0x1f, 0xcb,
0x22, 0x09, 0xb3, 0xfe, 0xf3, 0x1b, 0xe9, 0x55, 0xd4, 0x32, 0x5e, 0xfc, 0x0d, 0x00, 0x00, 0xff,
0xff, 0x48, 0x74, 0xbe, 0xc1, 0x64, 0x04, 0x00, 0x00,
}
func (m *WasmPlugin) Marshal() (dAtA []byte, err error) {
@@ -423,6 +443,18 @@ func (m *WasmPlugin) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if m.DefaultConfigDisable {
i--
if m.DefaultConfigDisable {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x6
i--
dAtA[i] = 0xb8
}
if len(m.MatchRules) > 0 {
for iNdEx := len(m.MatchRules) - 1; iNdEx >= 0; iNdEx-- {
{
@@ -549,6 +581,16 @@ func (m *MatchRule) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if m.ConfigDisable {
i--
if m.ConfigDisable {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x20
}
if m.Config != nil {
{
size, err := m.Config.MarshalToSizedBuffer(dAtA[:i])
@@ -643,6 +685,9 @@ func (m *WasmPlugin) Size() (n int) {
n += 2 + l + sovWasm(uint64(l))
}
}
if m.DefaultConfigDisable {
n += 3
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -671,6 +716,9 @@ func (m *MatchRule) Size() (n int) {
l = m.Config.Size()
n += 1 + l + sovWasm(uint64(l))
}
if m.ConfigDisable {
n += 2
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -1052,6 +1100,26 @@ func (m *WasmPlugin) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 103:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field DefaultConfigDisable", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowWasm
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.DefaultConfigDisable = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipWasm(dAtA[iNdEx:])
@@ -1203,6 +1271,26 @@ func (m *MatchRule) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ConfigDisable", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowWasm
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.ConfigDisable = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipWasm(dAtA[iNdEx:])

View File

@@ -104,6 +104,8 @@ message WasmPlugin {
google.protobuf.Struct default_config = 101;
// Extended by Higress, matching rules take effect
repeated MatchRule match_rules = 102;
// diable the default config
bool default_config_disable = 103;
}
// Extended by Higress
@@ -111,6 +113,7 @@ message MatchRule {
repeated string ingress = 1;
repeated string domain = 2;
google.protobuf.Struct config = 3;
bool config_disable = 4;
}
// The phase in the filter chain where the plugin will be injected.

View File

@@ -35,6 +35,8 @@ spec:
defaultConfig:
type: object
x-kubernetes-preserve-unknown-fields: true
defaultConfigDisable:
type: boolean
imagePullPolicy:
description: The pull behaviour to be applied when fetching an OCI
image.
@@ -52,6 +54,8 @@ spec:
config:
type: object
x-kubernetes-preserve-unknown-fields: true
configDisable:
type: boolean
domain:
items:
type: string

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 0.7.0
appVersion: 0.7.1
description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png
keywords:
@@ -9,4 +9,4 @@ name: higress-core
sources:
- http://github.com/alibaba/higress
type: application
version: 0.7.0
version: 0.7.1

View File

@@ -35,6 +35,8 @@ spec:
defaultConfig:
type: object
x-kubernetes-preserve-unknown-fields: true
defaultConfigDisable:
type: boolean
imagePullPolicy:
description: The pull behaviour to be applied when fetching an OCI
image.
@@ -52,6 +54,8 @@ spec:
config:
type: object
x-kubernetes-preserve-unknown-fields: true
configDisable:
type: boolean
domain:
items:
type: string

View File

@@ -86,4 +86,12 @@ higress: {{ include "controller.name" . }}
{{- else }}
{{- .Values.controller.serviceAccount.name | default "default" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "controller.jwtPolicy" -}}
{{- if semverCompare ">=1.21-0" .Capabilities.KubeVersion.GitVersion }}
{{- .Values.global.jwtPolicy | default "third-party-jwt" }}
{{- else }}
{{- print "first-party-jwt" }}
{{- end }}
{{- end }}

View File

@@ -4,6 +4,7 @@
trustDomain: "cluster.local"
accessLogEncoding: TEXT
accessLogFile: "/dev/stdout"
ingressControllerMode: "OFF"
accessLogFormat: '{"authority":"%REQ(:AUTHORITY)%","bytes_received":"%BYTES_RECEIVED%","bytes_sent":"%BYTES_SENT%","downstream_local_address":"%DOWNSTREAM_LOCAL_ADDRESS%","downstream_remote_address":"%DOWNSTREAM_REMOTE_ADDRESS%","duration":"%DURATION%","istio_policy_status":"%DYNAMIC_METADATA(istio.mixer:status)%","method":"%REQ(:METHOD)%","path":"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%","protocol":"%PROTOCOL%","request_id":"%REQ(X-REQUEST-ID)%","requested_server_name":"%REQUESTED_SERVER_NAME%","response_code":"%RESPONSE_CODE%","response_flags":"%RESPONSE_FLAGS%","route_name":"%ROUTE_NAME%","start_time":"%START_TIME%","trace_id":"%REQ(X-B3-TRACEID)%","upstream_cluster":"%UPSTREAM_CLUSTER%","upstream_host":"%UPSTREAM_HOST%","upstream_local_address":"%UPSTREAM_LOCAL_ADDRESS%","upstream_service_time":"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%","upstream_transport_failure_reason":"%UPSTREAM_TRANSPORT_FAILURE_REASON%","user_agent":"%REQ(USER-AGENT)%","x_forwarded_for":"%REQ(X-FORWARDED-FOR)%"}
'

View File

@@ -80,7 +80,7 @@ spec:
- name: REVISION
value: "{{ .Values.revision | default `default` }}"
- name: JWT_POLICY
value: {{ .Values.global.jwtPolicy }}
value: {{ include "controller.jwtPolicy" . }}
- name: PILOT_CERT_PROVIDER
value: "istiod"
- name: POD_NAME
@@ -147,7 +147,7 @@ spec:
volumeMounts:
- name: config
mountPath: /etc/istio/config
{{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
mountPath: /var/run/secrets/tokens
readOnly: true
@@ -241,7 +241,7 @@ spec:
- emptyDir:
medium: Memory
name: local-certs
{{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
projected:
sources:
@@ -249,6 +249,7 @@ spec:
audience: {{ .Values.global.sds.token.aud }}
expirationSeconds: 43200
path: istio-token
{{- end }}
# Optional: user-generated root
- name: cacerts
secret:
@@ -264,4 +265,3 @@ spec:
name: pilot-jwks-extra-cacerts{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -129,7 +129,7 @@ spec:
- name: ENABLE_INGRESS_GATEWAY_SDS
value: "false"
- name: JWT_POLICY
value: {{ .Values.global.jwtPolicy }}
value: {{ include "controller.jwtPolicy" . }}
- name: ISTIO_META_HTTP10
value: "1"
- name: ISTIO_META_CLUSTER_ID
@@ -177,7 +177,7 @@ spec:
{{- toYaml .Values.gateway.resources | nindent 12 }}
{{- end }}
volumeMounts:
{{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
mountPath: /var/run/secrets/tokens
readOnly: true
@@ -213,7 +213,7 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
projected:
sources:

View File

@@ -455,7 +455,7 @@ controller:
name: "higress-controller"
replicas: 1
image: higress
tag: "0.7.0"
tag: "0.7.1"
env: {}
labels: {}

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 0.7.0
version: 0.7.1
- name: higress-console
repository: https://higress.io/helm-charts/
version: 0.1.0
digest: sha256:3fd6cfb0fd10178927569d57e0cbff5250870dd677cbf30995c49ced83e38f7c
generated: "2023-03-15T15:09:02.878072+08:00"
version: 0.1.1
digest: sha256:051fbd7b2916d1d0c26839d0e27653f6e42d20e9294bd9eed9628f24c5a7b228
generated: "2023-04-03T13:42:23.705379+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 0.7.0
appVersion: 0.7.1
description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png
keywords:
@@ -11,9 +11,9 @@ sources:
dependencies:
- name: higress-core
repository: "file://../core"
version: 0.7.0
version: 0.7.1
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 0.1.0
version: 0.1.1
type: application
version: 0.7.0
version: 0.7.1

View File

@@ -0,0 +1,20 @@
diff --color -Naur external/istio/pkg/wasm/imagefetcher.go external/istio_new/pkg/wasm/imagefetcher.go
--- istio/pkg/wasm/imagefetcher.go 2023-03-19 17:56:55.238354950 +0800
+++ istio_new/pkg/wasm/imagefetcher.go 2023-03-19 17:56:40.630410241 +0800
@@ -176,12 +176,12 @@
return nil, fmt.Errorf("could not fetch layers: %v", err)
}
- // The image must be single-layered.
- if len(layers) != 1 {
- return nil, fmt.Errorf("number of layers must be 1 but got %d", len(layers))
+ // The image must have at least one layer.
+ if len(layers) == 0 {
+ return nil, fmt.Errorf("number of layers must be greater than zero")
}
- layer := layers[0]
+ layer := layers[len(layers)-1]
mt, err := layer.MediaType()
if err != nil {
return nil, fmt.Errorf("could not get media type: %v", err)

View File

@@ -707,7 +707,10 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
if result.PluginConfig != nil {
return result, nil
}
result.PluginConfig = obj.DefaultConfig
if !obj.DefaultConfigDisable {
result.PluginConfig = obj.DefaultConfig
}
hasValidRule := false
if len(obj.MatchRules) > 0 {
if result.PluginConfig == nil {
result.PluginConfig = &types.Struct{
@@ -716,6 +719,9 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
}
var ruleValues []*types.Value
for _, rule := range obj.MatchRules {
if rule.ConfigDisable {
continue
}
if rule.Config == nil {
return nil, errors.New("invalid rule has no config")
}
@@ -764,14 +770,20 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
Kind: v,
})
}
result.PluginConfig.Fields["_rules_"] = &types.Value{
Kind: &types.Value_ListValue{
ListValue: &types.ListValue{
Values: ruleValues,
if len(ruleValues) > 0 {
hasValidRule = true
result.PluginConfig.Fields["_rules_"] = &types.Value{
Kind: &types.Value_ListValue{
ListValue: &types.ListValue{
Values: ruleValues,
},
},
},
}
}
}
if !hasValidRule && obj.DefaultConfigDisable {
return nil, nil
}
return result, nil
}
@@ -802,6 +814,14 @@ func (m *IngressConfig) AddOrUpdateWasmPlugin(clusterNamespacedName util.Cluster
IngressLog.Errorf("invalid wasmPlugin:%s, err:%v", clusterNamespacedName.Name, err)
return
}
if istioWasmPlugin == nil {
IngressLog.Infof("wasmPlugin:%s will not be transferred to istio since config disabled",
clusterNamespacedName.Name)
m.mutex.Lock()
delete(m.wasmPlugins, clusterNamespacedName.Name)
m.mutex.Unlock()
return
}
IngressLog.Debugf("wasmPlugin:%s convert to istioWasmPlugin:%v", clusterNamespacedName.Name, istioWasmPlugin)
m.mutex.Lock()
m.wasmPlugins[clusterNamespacedName.Name] = istioWasmPlugin

View File

@@ -67,6 +67,8 @@ type Ingress struct {
IgnoreCase *IgnoreCaseConfig
Match *MatchConfig
HeaderControl *HeaderControlConfig
}
func (i *Ingress) NeedRegexMatch() bool {
@@ -138,6 +140,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
destination{},
ignoreCaseMatching{},
match{},
headerControl{},
},
gatewayHandlers: []GatewayHandler{
downstreamTLS{},
@@ -154,6 +157,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
fallback{},
ignoreCaseMatching{},
match{},
headerControl{},
},
trafficPolicyHandlers: []TrafficPolicyHandler{
upstreamTLS{},

View File

@@ -106,6 +106,8 @@ func ApplyByWeight(canary, route *networking.HTTPRoute, canaryIngress *Ingress)
// We will process total weight in the end.
route.Route = append(route.Route, canary.Route[0])
// canary route use the header control applied on itself.
headerControl{}.ApplyRoute(canary, canaryIngress)
// Move route level to destination level
canary.Route[0].Headers = canary.Headers
@@ -166,6 +168,10 @@ func ApplyByHeader(canary, route *networking.HTTPRoute, canaryIngress *Ingress)
}
}
canary.Headers = nil
// canary route use the header control applied on itself.
headerControl{}.ApplyRoute(canary, canaryIngress)
// First add normal route cluster
canary.Route[0].FallbackClusters = append(canary.Route[0].FallbackClusters,
route.Route[0].Destination.DeepCopy())

View File

@@ -73,10 +73,14 @@ func (a destination) Parse(annotations Annotations, config *Ingress, _ *GlobalCo
}
address := pairs[addrIndex]
host := address
var port string
var port uint64
colon := strings.LastIndex(address, ":")
if colon != -1 {
host, port = address[:colon], address[colon+1:]
var err error
port, err = strconv.ParseUint(address[colon+1:], 10, 32)
if err == nil && port > 0 && port < 65536 {
host = address[:colon]
}
}
var subset string
if len(pairs) >= addrIndex+2 {
@@ -89,14 +93,9 @@ func (a destination) Parse(annotations Annotations, config *Ingress, _ *GlobalCo
},
Weight: int32(weight),
}
if port != "" {
portNumber, err := strconv.ParseUint(port, 10, 32)
if err != nil {
IngressLog.Errorf("destination addr %s has invalid port %s within ingress %s/%s", address, port, config.Namespace, config.Name)
return nil
}
if port > 0 {
dest.Destination.Port = &networking.PortSelector{
Number: uint32(portNumber),
Number: uint32(port),
}
}
IngressLog.Debugf("destination generated for ingress %s/%s: %v", config.Namespace, config.Name, dest)

View File

@@ -84,6 +84,39 @@ func TestDestinationParse(t *testing.T) {
WeightSum: 100,
},
},
{
input: Annotations{
buildHigressAnnotationKey(destinationKey): "providers:com.alibaba.nacos.example.dubbo.service.DemoService:1.0.0:.DEFAULT-GROUP.public.nacos",
},
expect: &DestinationConfig{
McpDestination: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "providers:com.alibaba.nacos.example.dubbo.service.DemoService:1.0.0:.DEFAULT-GROUP.public.nacos",
},
Weight: 100,
},
},
WeightSum: 100,
},
},
{
input: Annotations{
buildHigressAnnotationKey(destinationKey): "providers:com.alibaba.nacos.example.dubbo.service.DemoService:1.0.0:.DEFAULT-GROUP.public.nacos:8080",
},
expect: &DestinationConfig{
McpDestination: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "providers:com.alibaba.nacos.example.dubbo.service.DemoService:1.0.0:.DEFAULT-GROUP.public.nacos",
Port: &networking.PortSelector{Number: 8080},
},
Weight: 100,
},
},
WeightSum: 100,
},
},
}
for _, testCase := range testCases {

View File

@@ -0,0 +1,160 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package annotations
import (
"strings"
networking "istio.io/api/networking/v1alpha3"
. "github.com/alibaba/higress/pkg/ingress/log"
)
const (
// request
requestHeaderAdd = "request-header-control-add"
requestHeaderUpdate = "request-header-control-update"
requestHeaderRemove = "request-header-control-remove"
// response
responseHeaderAdd = "response-header-control-add"
responseHeaderUpdate = "response-header-control-update"
responseHeaderRemove = "response-header-control-remove"
)
var (
_ Parser = headerControl{}
_ RouteHandler = headerControl{}
)
type HeaderOperation struct {
Add map[string]string
Update map[string]string
Remove []string
}
// HeaderControlConfig enforces header operations on route level.
// Note: Canary route don't use header control applied on the normal route.
type HeaderControlConfig struct {
Request *HeaderOperation
Response *HeaderOperation
}
type headerControl struct{}
func (h headerControl) Parse(annotations Annotations, config *Ingress, _ *GlobalContext) error {
if !needHeaderControlConfig(annotations) {
return nil
}
config.HeaderControl = &HeaderControlConfig{}
var requestAdd map[string]string
var requestUpdate map[string]string
var requestRemove []string
if add, err := annotations.ParseStringForHigress(requestHeaderAdd); err == nil {
requestAdd = convertAddOrUpdate(add)
}
if update, err := annotations.ParseStringForHigress(requestHeaderUpdate); err == nil {
requestUpdate = convertAddOrUpdate(update)
}
if remove, err := annotations.ParseStringForHigress(requestHeaderRemove); err == nil {
requestRemove = splitBySeparator(remove, ",")
}
if len(requestAdd) > 0 || len(requestUpdate) > 0 || len(requestRemove) > 0 {
config.HeaderControl.Request = &HeaderOperation{
Add: requestAdd,
Update: requestUpdate,
Remove: requestRemove,
}
}
var responseAdd map[string]string
var responseUpdate map[string]string
var responseRemove []string
if add, err := annotations.ParseStringForHigress(responseHeaderAdd); err == nil {
responseAdd = convertAddOrUpdate(add)
}
if update, err := annotations.ParseStringForHigress(responseHeaderUpdate); err == nil {
responseUpdate = convertAddOrUpdate(update)
}
if remove, err := annotations.ParseStringForHigress(responseHeaderRemove); err == nil {
responseRemove = splitBySeparator(remove, ",")
}
if len(responseAdd) > 0 || len(responseUpdate) > 0 || len(responseRemove) > 0 {
config.HeaderControl.Response = &HeaderOperation{
Add: responseAdd,
Update: responseUpdate,
Remove: responseRemove,
}
}
return nil
}
func (h headerControl) ApplyRoute(route *networking.HTTPRoute, config *Ingress) {
headerControlConfig := config.HeaderControl
if headerControlConfig == nil {
return
}
headers := &networking.Headers{
Request: &networking.Headers_HeaderOperations{},
Response: &networking.Headers_HeaderOperations{},
}
if headerControlConfig.Request != nil {
headers.Request.Add = headerControlConfig.Request.Add
headers.Request.Set = headerControlConfig.Request.Update
headers.Request.Remove = headerControlConfig.Request.Remove
}
if headerControlConfig.Response != nil {
headers.Response.Add = headerControlConfig.Response.Add
headers.Response.Set = headerControlConfig.Response.Update
headers.Response.Remove = headerControlConfig.Response.Remove
}
route.Headers = headers
}
func needHeaderControlConfig(annotations Annotations) bool {
return annotations.HasHigress(requestHeaderAdd) ||
annotations.HasHigress(requestHeaderUpdate) ||
annotations.HasHigress(requestHeaderRemove) ||
annotations.HasHigress(responseHeaderAdd) ||
annotations.HasHigress(responseHeaderUpdate) ||
annotations.HasHigress(responseHeaderRemove)
}
func convertAddOrUpdate(headers string) map[string]string {
result := map[string]string{}
parts := strings.Split(headers, "\n")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
keyValue := strings.Fields(part)
if len(keyValue) != 2 {
IngressLog.Errorf("Header format %s is invalid.", keyValue)
continue
}
key := strings.TrimSpace(keyValue[0])
value := strings.TrimSpace(keyValue[1])
result[key] = value
}
return result
}

View File

@@ -0,0 +1,235 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package annotations
import (
"reflect"
"testing"
networking "istio.io/api/networking/v1alpha3"
)
func TestHeaderControlParse(t *testing.T) {
headerControl := &headerControl{}
inputCases := []struct {
input map[string]string
expect *HeaderControlConfig
}{
{},
{
input: map[string]string{
buildHigressAnnotationKey(requestHeaderAdd): "one 1",
buildHigressAnnotationKey(responseHeaderAdd): "A a",
},
expect: &HeaderControlConfig{
Request: &HeaderOperation{
Add: map[string]string{
"one": "1",
},
},
Response: &HeaderOperation{
Add: map[string]string{
"A": "a",
},
},
},
},
{
input: map[string]string{
buildHigressAnnotationKey(requestHeaderAdd): "one 1\n two 2\nthree 3 \n",
buildHigressAnnotationKey(requestHeaderUpdate): "two 2",
buildHigressAnnotationKey(requestHeaderRemove): "one, two,three\n",
buildHigressAnnotationKey(responseHeaderAdd): "A a\nB b\n",
buildHigressAnnotationKey(responseHeaderUpdate): "X x\nY y\n",
buildHigressAnnotationKey(responseHeaderRemove): "x",
},
expect: &HeaderControlConfig{
Request: &HeaderOperation{
Add: map[string]string{
"one": "1",
"two": "2",
"three": "3",
},
Update: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
Response: &HeaderOperation{
Add: map[string]string{
"A": "a",
"B": "b",
},
Update: map[string]string{
"X": "x",
"Y": "y",
},
Remove: []string{"x"},
},
},
},
}
for _, inputCase := range inputCases {
t.Run("", func(t *testing.T) {
config := &Ingress{}
_ = headerControl.Parse(inputCase.input, config, nil)
if !reflect.DeepEqual(inputCase.expect, config.HeaderControl) {
t.Fatal("Should be equal")
}
})
}
}
func TestHeaderControlApplyRoute(t *testing.T) {
headerControl := headerControl{}
inputCases := []struct {
config *Ingress
input *networking.HTTPRoute
expect *networking.HTTPRoute
}{
{
config: &Ingress{},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{},
},
{
config: &Ingress{
HeaderControl: &HeaderControlConfig{},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{},
Response: &networking.Headers_HeaderOperations{},
},
},
},
{
config: &Ingress{
HeaderControl: &HeaderControlConfig{
Request: &HeaderOperation{
Add: map[string]string{
"one": "1",
"two": "2",
"three": "3",
},
Update: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Add: map[string]string{
"one": "1",
"two": "2",
"three": "3",
},
Set: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
Response: &networking.Headers_HeaderOperations{},
},
},
},
{
config: &Ingress{
HeaderControl: &HeaderControlConfig{
Response: &HeaderOperation{
Add: map[string]string{
"A": "a",
"B": "b",
},
Update: map[string]string{
"X": "x",
"Y": "y",
},
Remove: []string{"x"},
},
},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{},
Response: &networking.Headers_HeaderOperations{
Add: map[string]string{
"A": "a",
"B": "b",
},
Set: map[string]string{
"X": "x",
"Y": "y",
},
Remove: []string{"x"},
},
},
},
},
{
config: &Ingress{
HeaderControl: &HeaderControlConfig{
Request: &HeaderOperation{
Update: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
Response: &HeaderOperation{
Add: map[string]string{
"A": "a",
"B": "b",
},
Remove: []string{"x"},
},
},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Set: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
Response: &networking.Headers_HeaderOperations{
Add: map[string]string{
"A": "a",
"B": "b",
},
Remove: []string{"x"},
},
},
},
},
}
for _, inputCase := range inputCases {
t.Run("", func(t *testing.T) {
headerControl.ApplyRoute(inputCase.input, inputCase.config)
if !reflect.DeepEqual(inputCase.input, inputCase.expect) {
t.Fatal("Should be equal")
}
})
}
}

View File

@@ -1,3 +1,7 @@
<p>
<a href="README_EN.md">English</a> | 中文
</p>
# 功能说明
`basic-auth`插件实现了基于 HTTP Basic Auth 标准进行认证鉴权的功能

View File

@@ -0,0 +1,117 @@
<p>
English | <a href="README.md">中文</a>
</p>
# Description
`basic-auth` plugin implements the function of authentication based on the HTTP Basic Auth standard.
# Configuration Fields
| Name | Type | Requirement | Default Value | Description |
| ----------- | --------------- | -------- | ------ | ---------------------------------------------------- |
| `consumers` | array of object | Required | - | Caller of the service for authentication of requests |
| `_rules_` | array of object | Optional | - | Configure access permission list for specific routes or domains to authenticate requests |
Filed descriptions of `consumers` items:
| Name | Type | Requirement | Default Value | Description |
| ------------ | ------ | ----------- | ------------- | ------------------------------------- |
| `credential` | string | Required | - | Credential for this consumer's access |
| `name` | string | Required | - | Name of this consumer |
Configuration field descriptions for each item in `_rules_` are as follows:
| Field Name | Data Type | Requirement | Default | Description |
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
| `_match_route_` | array of string | One of `_match_route_` or `_match_domain_` | - | Configure the routes to match for request authorization |
| `_match_domain_` | array of string | One of `_match_route_` , `_match_domain_` | - | Configure the domains to match for request authorization |
| `allow` | array of string | Required | - | Configure the consumer names allowed to access requests that match the match condition |
**Note:**
- If the `_rules_` field is not configured, authentication is enabled for all routes of the current gateway instance by default;
- For authenticated requests, `X-Mse-Consumer` field will be added to the request header to identify the name of the caller.
# Configuration Samples
## Enable Authentication and Authorization for specific routes or domains
The following configuration will enable Basic Auth authentication and authorization for specific routes or domains of the gateway. Note that the username and password in the credential information are separated by a ":", and the `credential` field cannot be repeated.
```yaml
# use the _rules_ field for fine-grained rule configuration.
consumers:
- credential: 'admin:123456'
name: consumer1
- credential: 'guest:abc'
name: consumer2
_rules_:
# rule 1: match by the route name.
- _match_route_:
- route-a
- route-b
allow:
- consumer1
# rule 2: match by the domain.
- _match_domain_:
- "*.example.com"
- test.com
allow:
- consumer2
```
In this sample, `route-a` and `route-b` specified in `_match_route_` are the route names filled in when creating gateway routes. When these two routes are matched, the caller with `name` as `consumer1` is allowed to access, and other callers are not allowed to access.
The `*.example.com` and `test.com` specified in `_match_domain_` are used to match the domain name of the request. When the domain name is matched, the caller with `name` as `consumer2` is allowed to access, and other callers are not allowed to access.
### According to this configuration, the following requests are allowed:
**Requests with specified username and password**
```bash
# Assuming the following request will match with route-a
# Use -u option of curl to specify the credentials
curl -u admin:123456 http://xxx.hello.com/test
# Or specify the Authorization request header directly with the credentials in base64 encoding
curl -H 'Authorization: Basic YWRtaW46MTIzNDU2' http://xxx.hello.com/test
```
A `X-Mse-Consumer` field will be added to the headers of the request, and its value in this example is `consumer1`, used to identify the name of the caller when passed authentication and authorization.
### The following requests will be denied:
**Requests without providing username and password, returning 401**
```bash
curl http://xxx.hello.com/test
```
**Requests with incorrect username or password, returning 401**
```bash
curl -u admin:abc http://xxx.hello.com/test
```
**Requests matched with a caller who has no access permission, returning 403**
```bash
# consumer2 is not in the allow list of route-a
curl -u guest:abc http://xxx.hello.com/test
```
## Enable basic auth for gateway instance
The following configuration does not specify the `_rules_` field, so Basic Auth authentication will be effective for the whole gateway instance.
```yaml
consumers:
- credential: 'admin:123456'
name: consumer1
- credential: 'guest:abc'
name: consumer2
```
# Error Codes
| HTTP Status Code | Error Info | Reason |
| ----------- | ------------------------------------------------------------------------------ | ---------------------- |
| 401 | Request denied by Basic Auth check. No Basic Authentication information found. | Credentials not provided in the request |
| 401 | Request denied by Basic Auth check. Invalid username and/or password | Invalid username and/or password |
| 403 | Request denied by Basic Auth check. Unauthorized consumer | Unauthorized consumer |

View File

@@ -44,7 +44,7 @@ consumers:
name: consumer2
keys:
- apikey
- x-api-key
in_query: true
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
@@ -109,7 +109,7 @@ consumers:
name: consumer2
keys:
- apikey
- x-api-key
in_query: true
```
# 相关错误码

164
registry/direct/watcher.go Normal file
View File

@@ -0,0 +1,164 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package direct
import (
"net"
"regexp"
"strconv"
"strings"
"sync"
"istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config/protocol"
"istio.io/pkg/log"
apiv1 "github.com/alibaba/higress/api/networking/v1"
"github.com/alibaba/higress/pkg/common"
"github.com/alibaba/higress/registry"
provider "github.com/alibaba/higress/registry"
"github.com/alibaba/higress/registry/memory"
)
type watcher struct {
provider.BaseWatcher
apiv1.RegistryConfig
cache memory.Cache
mutex sync.Mutex
}
type WatcherOption func(w *watcher)
func NewWatcher(cache memory.Cache, opts ...WatcherOption) (provider.Watcher, error) {
w := &watcher{
cache: cache,
}
for _, opt := range opts {
opt(w)
}
return w, nil
}
func WithType(t string) WatcherOption {
return func(w *watcher) {
w.Type = t
}
}
func WithName(name string) WatcherOption {
return func(w *watcher) {
w.Name = name
}
}
func WithDomain(domain string) WatcherOption {
return func(w *watcher) {
w.Domain = domain
}
}
func WithPort(port uint32) WatcherOption {
return func(w *watcher) {
w.Port = port
}
}
func (w *watcher) Run() {
w.mutex.Lock()
defer w.mutex.Unlock()
host := strings.Join([]string{w.Name, w.Type}, common.DotSeparator)
serviceEntry := w.generateServiceEntry(host)
if serviceEntry != nil {
w.cache.UpdateServiceEntryWrapper(host, &memory.ServiceEntryWrapper{
ServiceName: w.Name,
ServiceEntry: serviceEntry,
Suffix: w.Type,
RegistryType: w.Type,
})
w.UpdateService()
}
w.Ready(true)
}
func (w *watcher) Stop() {
w.mutex.Lock()
defer w.mutex.Unlock()
host := strings.Join([]string{w.Name, w.Type}, common.DotSeparator)
w.cache.DeleteServiceEntryWrapper(host)
w.Ready(false)
}
var domainRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$`)
func (w *watcher) generateServiceEntry(host string) *v1alpha3.ServiceEntry {
endpoints := make([]*v1alpha3.WorkloadEntry, 0)
for _, ep := range strings.Split(w.Domain, common.CommaSeparator) {
var endpoint *v1alpha3.WorkloadEntry
if w.Type == string(registry.Static) {
pair := strings.Split(ep, common.ColonSeparator)
if len(pair) != 2 {
log.Errorf("invalid endpoint:%s with static type", ep)
return nil
}
port, err := strconv.ParseUint(pair[1], 10, 32)
if err != nil {
log.Errorf("invalid port:%s of endpoint:%s", pair[1], ep)
return nil
}
if net.ParseIP(pair[0]) == nil {
log.Errorf("invalid ip:%s of endpoint:%s", pair[0], ep)
return nil
}
endpoint = &v1alpha3.WorkloadEntry{
Address: pair[0],
Ports: map[string]uint32{"http": uint32(port)},
}
} else if w.Type == string(registry.DNS) {
if !domainRegex.MatchString(ep) {
log.Errorf("invalid domain format:%s", ep)
return nil
}
endpoint = &v1alpha3.WorkloadEntry{
Address: ep,
}
} else {
log.Errorf("unknown direct service type:%s", w.Type)
return nil
}
endpoints = append(endpoints, endpoint)
}
if len(endpoints) == 0 {
log.Errorf("empty endpoints will not be pushed, host:%s", host)
return nil
}
var ports []*v1alpha3.Port
ports = append(ports, &v1alpha3.Port{
Number: w.Port,
Name: "http",
Protocol: string(protocol.HTTP),
})
se := &v1alpha3.ServiceEntry{
Hosts: []string{host},
Ports: ports,
Location: v1alpha3.ServiceEntry_MESH_INTERNAL,
Endpoints: endpoints,
}
if w.Type == string(registry.Static) {
se.Resolution = v1alpha3.ServiceEntry_STATIC
} else if w.Type == string(registry.DNS) {
se.Resolution = v1alpha3.ServiceEntry_DNS
}
return se
}

View File

@@ -29,6 +29,7 @@ import (
type Cache interface {
UpdateServiceEntryWrapper(service string, data *ServiceEntryWrapper)
DeleteServiceEntryWrapper(service string)
PurgeStaleService()
UpdateServiceEntryEnpointWrapper(service, ip, regionId, zoneId, protocol string, labels map[string]string)
GetServiceByEndpoints(requestVersions, endpoints map[string]bool, versionKey string, protocol common.Protocol) map[string][]string
GetAllServiceEntry() []*v1alpha3.ServiceEntry
@@ -39,20 +40,22 @@ type Cache interface {
func NewCache() Cache {
return &store{
mux: &sync.RWMutex{},
sew: make(map[string]*ServiceEntryWrapper),
toBeUpdated: make([]*ServiceEntryWrapper, 0),
toBeDeleted: make([]*ServiceEntryWrapper, 0),
ip2services: make(map[string]map[string]bool),
mux: &sync.RWMutex{},
sew: make(map[string]*ServiceEntryWrapper),
toBeUpdated: make([]*ServiceEntryWrapper, 0),
toBeDeleted: make([]*ServiceEntryWrapper, 0),
ip2services: make(map[string]map[string]bool),
deferedDelete: make(map[string]struct{}),
}
}
type store struct {
mux *sync.RWMutex
sew map[string]*ServiceEntryWrapper
toBeUpdated []*ServiceEntryWrapper
toBeDeleted []*ServiceEntryWrapper
ip2services map[string]map[string]bool
mux *sync.RWMutex
sew map[string]*ServiceEntryWrapper
toBeUpdated []*ServiceEntryWrapper
toBeDeleted []*ServiceEntryWrapper
ip2services map[string]map[string]bool
deferedDelete map[string]struct{}
}
func (s *store) UpdateServiceEntryEnpointWrapper(service, ip, regionId, zoneId, protocol string, labels map[string]string) {
@@ -105,6 +108,12 @@ func (s *store) UpdateServiceEntryWrapper(service string, data *ServiceEntryWrap
s.toBeUpdated = append(s.toBeUpdated, data)
s.sew[service] = data
// service is updated, should not be deleted
if _, ok := s.deferedDelete[service]; ok {
delete(s.deferedDelete, service)
log.Debugf("service in deferedDelete updated, host:%s", service)
}
log.Infof("ServiceEntry updated, host:%s", service)
}
func (s *store) DeleteServiceEntryWrapper(service string) {
@@ -114,7 +123,18 @@ func (s *store) DeleteServiceEntryWrapper(service string) {
if data, exist := s.sew[service]; exist {
s.toBeDeleted = append(s.toBeDeleted, data)
}
delete(s.sew, service)
s.deferedDelete[service] = struct{}{}
}
// should only be called when reconcile is done
func (s *store) PurgeStaleService() {
s.mux.Lock()
defer s.mux.Unlock()
for service := range s.deferedDelete {
delete(s.sew, service)
delete(s.deferedDelete, service)
log.Infof("ServiceEntry deleted, host:%s", service)
}
}
// GetServiceByEndpoints get the list of services of which "address:port" contained by the endpoints

View File

@@ -60,8 +60,6 @@ type watcher struct {
RegistryType provider.ServiceRegistryType `json:"registry_type"`
Status provider.WatcherStatus `json:"status"`
namingClient naming_client.INamingClient
updateHandler provider.ServiceUpdateHandler
readyHandler provider.ReadyHandler
cache memory.Cache
mutex *sync.Mutex
stop chan struct{}
@@ -234,7 +232,7 @@ func (w *watcher) Run() {
if err != nil {
log.Errorf("first fetch services failed, err:%v", err)
} else {
w.readyHandler(true)
w.Ready(true)
}
for {
select {
@@ -243,7 +241,7 @@ func (w *watcher) Run() {
if err != nil {
log.Errorf("fetch services failed, err:%v", err)
} else {
w.readyHandler(true)
w.Ready(true)
}
case <-w.stop:
return
@@ -399,7 +397,7 @@ func (w *watcher) getSubscribeCallback(groupName string, serviceName string) fun
host := strings.Join([]string{serviceName, suffix}, common.DotSeparator)
return func(services []model.Instance, err error) {
defer w.updateHandler()
defer w.UpdateService()
//log.Info("callback", "serviceName", serviceName, "suffix", suffix, "details", services)
@@ -484,8 +482,8 @@ func (w *watcher) Stop() {
}
w.isStop = true
w.stop <- struct{}{}
w.readyHandler(false)
close(w.stop)
w.Ready(false)
}
func (w *watcher) IsHealthy() bool {
@@ -496,14 +494,6 @@ func (w *watcher) GetRegistryType() string {
return w.RegistryType.String()
}
func (w *watcher) AppendServiceUpdateHandler(f func()) {
w.updateHandler = f
}
func (w *watcher) ReadyHandler(f func(bool)) {
w.readyHandler = f
}
func shouldSubscribe(serviceName string) bool {
prefixFilters := []string{"consumers:"}
fullFilters := []string{""}

View File

@@ -58,8 +58,6 @@ type watcher struct {
RegistryType provider.ServiceRegistryType `json:"registry_type"`
Status provider.WatcherStatus `json:"status"`
namingClient naming_client.INamingClient
updateHandler provider.ServiceUpdateHandler
readyHandler provider.ReadyHandler
cache memory.Cache
mutex *sync.Mutex
stop chan struct{}
@@ -200,7 +198,7 @@ func (w *watcher) Run() {
defer ticker.Stop()
w.Status = provider.ProbeWatcherStatus(w.Domain, strconv.FormatUint(uint64(w.Port), 10))
w.fetchAllServices()
w.readyHandler(true)
w.Ready(true)
for {
select {
case <-ticker.C:
@@ -218,7 +216,6 @@ func (w *watcher) fetchAllServices() error {
return nil
}
fetchedServices := make(map[string]bool)
for _, groupName := range w.NacosGroups {
for page := 1; ; page++ {
ss, err := w.namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
@@ -305,7 +302,7 @@ func (w *watcher) getSubscribeCallback(groupName string, serviceName string) fun
host := strings.Join([]string{serviceName, suffix}, common.DotSeparator)
return func(services []model.SubscribeService, err error) {
defer w.updateHandler()
defer w.UpdateService()
//log.Info("callback", "serviceName", serviceName, "suffix", suffix, "details", services)
@@ -388,8 +385,8 @@ func (w *watcher) Stop() {
w.cache.DeleteServiceEntryWrapper(host)
}
w.isStop = true
w.stop <- struct{}{}
w.readyHandler(false)
close(w.stop)
w.Ready(false)
}
func (w *watcher) IsHealthy() bool {
@@ -400,14 +397,6 @@ func (w *watcher) GetRegistryType() string {
return w.RegistryType.String()
}
func (w *watcher) AppendServiceUpdateHandler(f func()) {
w.updateHandler = f
}
func (w *watcher) ReadyHandler(f func(bool)) {
w.readyHandler = f
}
func shouldSubscribe(serviceName string) bool {
prefixFilters := []string{"consumers:"}
fullFilters := []string{""}

View File

@@ -25,6 +25,7 @@ import (
apiv1 "github.com/alibaba/higress/api/networking/v1"
v1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
. "github.com/alibaba/higress/registry"
"github.com/alibaba/higress/registry/direct"
"github.com/alibaba/higress/registry/memory"
"github.com/alibaba/higress/registry/nacos"
nacosv2 "github.com/alibaba/higress/registry/nacos/v2"
@@ -77,6 +78,26 @@ func (r *Reconciler) Reconcile(mcpbridge *v1.McpBridge) {
errHappened := false
log.Infof("ReconcileRegistries, toBeCreated: %d, toBeUpdated: %d, toBeDeleted: %d",
len(toBeCreated), len(toBeUpdated), len(toBeDeleted))
for k := range toBeDeleted {
r.watchers[k].Stop()
delete(r.registries, k)
delete(r.watchers, k)
}
for k, v := range toBeUpdated {
r.watchers[k].Stop()
delete(r.registries, k)
delete(r.watchers, k)
watcher, err := r.generateWatcherFromRegistryConfig(v, &wg)
if err != nil {
errHappened = true
log.Errorf("ReconcileRegistries failed, err:%v", err)
continue
}
go watcher.Run()
r.watchers[k] = watcher
r.registries[k] = v
}
for k, v := range toBeCreated {
watcher, err := r.generateWatcherFromRegistryConfig(v, &wg)
if err != nil {
@@ -89,31 +110,12 @@ func (r *Reconciler) Reconcile(mcpbridge *v1.McpBridge) {
r.watchers[k] = watcher
r.registries[k] = v
}
for k, v := range toBeUpdated {
go r.watchers[k].Stop()
delete(r.registries, k)
delete(r.watchers, k)
watcher, err := r.generateWatcherFromRegistryConfig(v, &wg)
if err != nil {
errHappened = true
log.Errorf("ReconcileRegistries failed, err:%v", err)
continue
}
go watcher.Run()
r.watchers[k] = watcher
r.registries[k] = v
}
for k := range toBeDeleted {
go r.watchers[k].Stop()
delete(r.registries, k)
delete(r.watchers, k)
}
if errHappened {
log.Error("ReconcileRegistries failed, Init Watchers failed")
return
}
wg.Wait()
r.Cache.PurgeStaleService()
log.Infof("Registries is reconciled")
}
@@ -158,6 +160,14 @@ func (r *Reconciler) generateWatcherFromRegistryConfig(registry *apiv1.RegistryC
zookeeper.WithPort(registry.Port),
zookeeper.WithZkServicesPath(registry.ZkServicesPath),
)
case string(Static), string(DNS):
watcher, err = direct.NewWatcher(
r.Cache,
direct.WithType(registry.Type),
direct.WithName(registry.Name),
direct.WithDomain(registry.Domain),
direct.WithPort(registry.Port),
)
default:
return nil, errors.New("unsupported registry type:" + registry.Type)
}
@@ -172,7 +182,7 @@ func (r *Reconciler) generateWatcherFromRegistryConfig(registry *apiv1.RegistryC
once.Do(func() {
wg.Done()
if ready {
log.Infof("Registry Watcher is ready, type:%s, name:%s", registry.Type, registry.Name)
log.Infof("Registry Watcher is ready, type:%s, name:%s", registry.Type, registry.Name)
}
})
})

View File

@@ -25,6 +25,8 @@ const (
Consul ServiceRegistryType = "consul"
Nacos ServiceRegistryType = "nacos"
Nacos2 ServiceRegistryType = "nacos2"
Static ServiceRegistryType = "static"
DNS ServiceRegistryType = "dns"
Healthy WatcherStatus = "healthy"
UnHealthy WatcherStatus = "unhealthy"
@@ -52,14 +54,21 @@ type Watcher interface {
ReadyHandler(f func(bool))
}
type BaseWatcher struct{}
type BaseWatcher struct {
UpdateService ServiceUpdateHandler
Ready ReadyHandler
}
func (w *BaseWatcher) Run() {}
func (w *BaseWatcher) Stop() {}
func (w *BaseWatcher) IsHealthy() bool { return true }
func (w *BaseWatcher) GetRegistryType() string { return "" }
func (w *BaseWatcher) AppendServiceUpdateHandler(f func()) {}
func (w *BaseWatcher) ReadyHandler(f func(bool)) {}
func (w *BaseWatcher) Run() {}
func (w *BaseWatcher) Stop() {}
func (w *BaseWatcher) IsHealthy() bool { return true }
func (w *BaseWatcher) GetRegistryType() string { return "" }
func (w *BaseWatcher) AppendServiceUpdateHandler(f func()) {
w.UpdateService = f
}
func (w *BaseWatcher) ReadyHandler(f func(bool)) {
w.Ready = f
}
type ServiceUpdateHandler func()
type ReadyHandler func(bool)

View File

@@ -50,8 +50,6 @@ type watcher struct {
RegistryType provider.ServiceRegistryType `json:"registry_type"`
Status provider.WatcherStatus `json:"status"`
serviceRemaind *atomic.Int32
updateHandler provider.ServiceUpdateHandler
readyHandler provider.ReadyHandler
cache memory.Cache
mutex *sync.Mutex
stop chan struct{}
@@ -103,27 +101,6 @@ func NewWatcher(cache memory.Cache, opts ...WatcherOption) (provider.Watcher, er
log.Info("connect zk error")
return nil, errors.New("connect zk error")
}
connectEvent := make(chan zk.Event, 2)
newClient.RegisterEvent("", connectEvent)
connectTimer := time.NewTimer(timeout)
connectTimout := false
FOR:
for {
select {
case ev := <-connectEvent:
if ev.State == zk.StateConnected {
break FOR
}
case <-connectTimer.C:
connectTimout = true
break FOR
}
}
if connectTimout {
return nil, errors.New("connect zk timeout")
}
log.Info("zk connected")
newClient.UnregisterEvent("", connectEvent)
w.reconnectCh = newClient.Reconnect()
w.zkClient = newClient
go func() {
@@ -360,7 +337,7 @@ func (w *watcher) DataChange(eventType Event) bool {
Suffix: "zookeeper",
RegistryType: w.Type,
})
w.updateHandler()
w.UpdateService()
} else if eventType.Action == EventTypeDel {
w.seMux.Lock()
value, ok := w.serviceEntry[host]
@@ -391,7 +368,7 @@ func (w *watcher) DataChange(eventType Event) bool {
RegistryType: w.Type,
})
}
w.updateHandler()
w.UpdateService()
}
return true
}
@@ -601,7 +578,7 @@ func (w *watcher) ChildToServiceEntry(children []string, interfaceName, zkPath s
}
}
w.seMux.Unlock()
w.updateHandler()
w.UpdateService()
}
}
@@ -702,7 +679,7 @@ func (w *watcher) Run() {
case <-ticker.C:
var needNewFetch bool
if w.IsReady() {
w.readyHandler(true)
w.Ready(true)
needNewFetch = true
}
if firstFetchErr != nil || needNewFetch {
@@ -733,15 +710,13 @@ func (w *watcher) Stop() {
for key := range w.serviceEntry {
w.cache.DeleteServiceEntryWrapper(key)
}
w.updateHandler()
w.UpdateService()
w.seMux.Unlock()
w.stop <- struct{}{}
w.Done <- struct{}{}
close(w.stop)
close(w.Done)
w.zkClient.Close()
w.readyHandler(false)
w.Ready(false)
}
func (w *watcher) IsHealthy() bool {
@@ -752,14 +727,6 @@ func (w *watcher) GetRegistryType() string {
return w.RegistryType.String()
}
func (w *watcher) AppendServiceUpdateHandler(f func()) {
w.updateHandler = f
}
func (w *watcher) ReadyHandler(f func(bool)) {
w.readyHandler = f
}
func (w *watcher) IsReady() bool {
if w.serviceRemaind == nil {
return true

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package tests
import (
"testing"
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteCanaryWeight)
}
var HTTPRouteCanaryWeight = suite.ConformanceTest{
ShortName: "HTTPRouteCanaryWeight",
Description: "The Ingress in the higress-conformance-infra namespace uses the canary weight traffic split.",
Manifests: []string{"tests/httproute-canary-weight.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
// test if the weight is 0
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/weight-0",
Host: "canary.higress.io",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
// test if the weight is 100
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v2",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/weight-100",
Host: "canary.higress.io",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
}
t.Run("Canary HTTPRoute Traffic Split", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,97 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed 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.
# Test if the weight is 0
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "0"
name: ingress-echo-canary-weight-0
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: canary.higress.io
http:
paths:
- path: /weight-0
pathType: Exact
backend:
service:
name: infra-backend-v3
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-echo-weight-0
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: canary.higress.io
http:
paths:
- path: /weight-0
pathType: Exact
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
# Test if the weight is 100
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "100"
name: ingress-echo-canary-weight-100
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: canary.higress.io
http:
paths:
- path: /weight-100
pathType: Exact
backend:
service:
name: infra-backend-v2
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-echo-weight-100
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: canary.higress.io
http:
paths:
- path: /weight-100
pathType: Exact
backend:
service:
name: infra-backend-v1
port:
number: 8080

View File

@@ -0,0 +1,130 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package tests
import (
"testing"
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteRequestHeaderControl)
}
var HTTPRouteRequestHeaderControl = suite.ConformanceTest{
ShortName: "HTTPRouteRequestHeaderControl",
Description: "A single Ingress in the higress-conformance-infra namespace controls the request header.",
Manifests: []string{"tests/httproute-request-header-control.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/foo1",
Host: "foo.com",
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Headers: map[string]string{
"stage": "test",
},
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
{
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/foo2",
Host: "foo.com",
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Headers: map[string]string{
"stage": "test",
"canary": "true",
},
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
{
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/foo3",
Host: "foo.com",
Headers: map[string]string{
"stage": "test",
},
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Headers: map[string]string{
"stage": "pro",
"canary": "true",
},
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
{
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/foo4",
Host: "foo.com",
Headers: map[string]string{
"stage": "test",
},
},
ExpectedRequest: &http.ExpectedRequest{
AbsentHeaders: []string{"stage"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
}
t.Run("Request header control", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,99 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed 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.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/request-header-control-add: stage test
name: httproute-request-header-control-add-one
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Exact
path: "/foo1"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/request-header-control-add: |
stage test
canary true
name: httproute-request-header-control-add-more
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Exact
path: "/foo2"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/request-header-control-update: stage pro
name: httproute-request-header-control-update
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Exact
path: "/foo3"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/request-header-control-remove: stage
name: httproute-request-header-control-remove
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Exact
path: "/foo4"
backend:
service:
name: infra-backend-v1
port:
number: 8080

View File

@@ -68,6 +68,7 @@ func TestHigressConformanceTests(t *testing.T) {
tests.HTTPRouteSameHostAndPath,
tests.HTTPRouteCanaryHeaderWithCustomizedHeader,
tests.HTTPRouteWhitelistSourceRange,
tests.HTTPRouteCanaryWeight,
}
cSuite.Run(t, higressTests)