Compare commits

...

11 Commits

Author SHA1 Message Date
Kent Dong
dc54c581f3 rel: Release version 1.1.2 (#545) 2023-09-20 11:27:36 +08:00
WeixinX
b47d74bce5 fix: Fix the image display in the E2E test README.md (#516) 2023-09-19 23:24:06 +08:00
Xunzhuo
8d8ad6d624 fix: remove non-existed namespaces and separate bases (#541)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-09-19 17:12:38 +08:00
Xunzhuo
8062625d75 feat: add features to conformance and do some refactors (#532)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-09-19 11:53:06 +08:00
WeixinX
54a8a906ae feat: Implement basic-auth WASM plugin using the Go SDK (#515) 2023-09-18 11:13:08 +08:00
Kent Dong
8659895a91 feat: Add update function to get-higress.sh (#523) 2023-09-18 11:06:37 +08:00
澄潭
dc3e496aa0 fix a concurrency issue of mcprbidge reconcile (#511) 2023-09-08 15:59:22 +08:00
Kent Dong
8747e1ddad feat: update get-higress.sh (#500) 2023-08-29 10:23:08 +08:00
船长
2b9e3a14c2 A plugin that implements token parsing and authentication function based on wasm-go (#485) 2023-08-27 15:57:15 +08:00
Jun
1051201e97 add consul cluster (#494) 2023-08-19 10:40:34 +08:00
Kent Dong
8b24a20651 feat: Update image tag management mechanisms (#495) 2023-08-19 10:40:04 +08:00
80 changed files with 2044 additions and 461 deletions

View File

@@ -147,6 +147,10 @@ jobs:
higress-wasmplugin-test:
runs-on: ubuntu-latest
needs: [build]
strategy:
matrix:
# TODO(Xunzhuo): Enable C WASM Filters in CI
wasmPluginType: [ GO ]
steps:
- uses: actions/checkout@v3
@@ -179,7 +183,7 @@ jobs:
- run: git stash # restore patch
- name: "Run Ingress WasmPlugins Tests"
run: GOPROXY="https://proxy.golang.org,direct" make higress-wasmplugin-test
run: GOPROXY="https://proxy.golang.org,direct" PLUGIN_TYPE=${{ matrix.wasmPluginType }} make higress-wasmplugin-test
publish:
runs-on: ubuntu-latest

View File

@@ -172,10 +172,9 @@ ENVOY_LATEST_IMAGE_TAG ?= 1.1.1
ISTIO_LATEST_IMAGE_TAG ?= 1.1.1
install-dev: pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'
install-dev-wasmplugin: build-wasmplugins pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true' --set 'global.volumeWasmPlugins=true'
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true' --set 'global.volumeWasmPlugins=true'
uninstall:
helm uninstall higress -n higress-system

View File

@@ -1 +1 @@
v1.1.1
v1.1.2

View File

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

View File

@@ -33,7 +33,7 @@ spec:
{{- if contains "/" .Values.pilot.image }}
image: "{{ .Values.pilot.image }}"
{{- else }}
image: "{{ .Values.pilot.hub | default .Values.global.hub }}/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Values.global.tag }}"
image: "{{ .Values.pilot.hub | default .Values.global.hub }}/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Chart.AppVersion }}"
{{- end }}
{{- if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}

View File

@@ -44,8 +44,6 @@ global:
# Releases are published to docker hub under 'istio' project.
# Dev builds from prow are on gcr.io
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
# Default tag for Istio images.
tag: 1.1.1
# Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
@@ -369,7 +367,7 @@ gateway:
name: "higress-gateway"
replicas: 2
image: gateway
tag: "1.1.1"
tag: ""
# revision declares which revision this gateway is a part of
revision: ""
@@ -457,7 +455,7 @@ controller:
name: "higress-controller"
replicas: 1
image: higress
tag: "1.1.1"
tag: ""
env: {}
labels: {}
@@ -547,7 +545,7 @@ pilot:
rollingMaxUnavailable: 25%
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
tag: 1.1.1
tag: ""
# Can be a full hub/image:tag
image: pilot

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 1.1.1
version: 1.1.2
- name: higress-console
repository: https://higress.io/helm-charts/
version: 1.1.1
digest: sha256:dd74a69c4031fa3e7798233602b44f0da3f657cbb40c61754298fbc877be2ae6
generated: "2023-08-10T10:54:46.8520756+08:00"
version: 1.1.2
digest: sha256:8fc099c4ad77bcdc4b9dde2ef14f89b2159b6fdcc49a3dc7e1cccb01a7ed99b9
generated: "2023-09-19T21:46:20.2567789+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 1.1.1
appVersion: 1.1.2
description: Helm chart for deploying Higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -12,9 +12,9 @@ sources:
dependencies:
- name: higress-core
repository: "file://../core"
version: 1.1.1
version: 1.1.2
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 1.1.1
version: 1.1.2
type: application
version: 1.1.1
version: 1.1.2

View File

@@ -29,6 +29,7 @@ import (
"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/types"
"github.com/golang/protobuf/ptypes/wrappers"
"go.uber.org/atomic"
"google.golang.org/protobuf/types/known/anypb"
extensions "istio.io/api/extensions/v1alpha1"
networking "istio.io/api/networking/v1alpha3"
@@ -109,7 +110,7 @@ type IngressConfig struct {
RegistryReconciler *reconcile.Reconciler
mcpbridgeReconciled bool
mcpbridgeReconciled *atomic.Bool
mcpbridgeController mcpbridge.McpBridgeController
@@ -154,7 +155,7 @@ func NewIngressConfig(localKubeClient kube.Client, XDSUpdater model.XDSUpdater,
common.CreateConvertedName(clusterId, "global"),
watchedSecretSet: sets.NewSet(),
namespace: namespace,
mcpbridgeReconciled: true,
mcpbridgeReconciled: atomic.NewBool(true),
wasmPlugins: make(map[string]*extensions.WasmPlugin),
http2rpcs: make(map[string]*higressv1.Http2Rpc),
}
@@ -947,9 +948,7 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN
clusterNamespacedName.Namespace, clusterNamespacedName.Name)
return
}
m.mutex.Lock()
m.mcpbridgeReconciled = false
m.mutex.Unlock()
m.mcpbridgeReconciled.Store(false)
if m.RegistryReconciler == nil {
m.RegistryReconciler = reconcile.NewReconciler(func() {
metadata := config.Meta{
@@ -966,12 +965,12 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN
}, m.localKubeClient, m.namespace)
}
reconciler := m.RegistryReconciler
go func() {
reconciler.Reconcile(mcpbridge)
m.mutex.Lock()
m.mcpbridgeReconciled = true
m.mutex.Unlock()
}()
err = reconciler.Reconcile(mcpbridge)
if err != nil {
IngressLog.Errorf("Mcpbridge reconcile failed, err:%v", err)
return
}
m.mcpbridgeReconciled.Store(true)
}
func (m *IngressConfig) DeleteMcpBridge(clusterNamespacedName util.ClusterNamespacedName) {
@@ -1405,7 +1404,7 @@ func (m *IngressConfig) HasSynced() bool {
return false
}
}
if !m.mcpbridgeController.HasSynced() || !m.mcpbridgeReconciled {
if !m.mcpbridgeController.HasSynced() || !m.mcpbridgeReconciled.Load() {
return false
}
if !m.wasmPluginController.HasSynced() {

View File

@@ -13,6 +13,7 @@ $ PLUGIN_NAME=request_block make build
<details>
<summary>Output</summary>
<pre><code>
DOCKER_BUILDKIT=1 docker build --build-arg PLUGIN_NAME=request_block \
-t request_block:20230721-141120-aa17e95 \
--output extensions/request_block \
@@ -106,16 +107,17 @@ spec:
The rules will be matched in the order of configuration. If one match is found, it will stop, and the matching configuration will take effect.
## E2E test
When you complete a GO plug-in function, you can create associated e2e test cases at the same time, and complete the test verification of the plug-in function locally.
### step1. write test cases
In the directory of `./ test/e2e/conformance/tests/`, add the xxx.yaml file and xxx.go file. Such as test for `request-block` wasm-plugin,
./test/e2e/conformance/tests/request-block.yaml
```
``` yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
...
@@ -126,16 +128,18 @@ spec:
- "swagger.html"
url: file:///opt/plugins/wasm-go/extensions/request-block/plugin.wasm
```
`Above of the url, the name of after extensions indicates the name of the folder where the plug-in resides.`
./test/e2e/conformance/tests/request-block.go
### step2. add test cases
Add the test cases written above to the e2e test list,
./test/e2e/e2e_test.go
```
```go
...
cSuite.Setup(t)
var higressTests []suite.ConformanceTest
@@ -160,8 +164,9 @@ cSuite.Setup(t)
```
### step3. compile and run test cases
Considering that building wasm locally is time-consuming, we support building only the plug-ins that need to be tested (at the same time, you can also temporarily modify the list of test cases in the second small step above, and only execute your newly written cases).
```bash
PLUGIN_TYPE=CPP PLUGIN_NAME=request_block make higress-wasmplugin-test
```
```

View File

@@ -0,0 +1,114 @@
---
title: Basic 认证
keywords: [higress,basic auth]
description: Basic 认证插件配置参考
---
## 功能说明
`basic-auth`插件实现了基于 HTTP Basic Auth 标准进行认证鉴权的功能
## 运行属性
插件执行阶段:`认证阶段`
插件执行优先级:`320`
## 配置字段
### 全局配置
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ----------- | --------------- | -------- | ------ | ---------------------------------------------------- |
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
| `global_auth` | bool | 选填 | - | 若配置为true则全局生效认证机制; 若配置为false则只对做了配置的域名和路由生效认证机制; 若不配置则仅当没有域名和路由配置时全局生效(兼容机制) |
`consumers`中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ------------ | -------- | -------- | ------ | ------------------------ |
| `credential` | string | 必填 | - | 配置该consumer的访问凭证 |
| `name` | string | 必填 | - | 配置该consumer的名称 |
### 域名和路由级配置
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求配置允许访问的consumer名称 |
**注意:**
- 对于通过认证鉴权的请求请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
## 配置示例
### 对特定路由或域名开启认证和鉴权
以下配置将对网关特定路由或域名开启 Basic Auth 认证和鉴权,注意凭证信息中的用户名和密码之间使用":"分隔,`credential`字段不能重复
**全局配置**
```yaml
consumers:
- credential: 'admin:123456'
name: consumer1
- credential: 'guest:abc'
name: consumer2
global_auth: false
```
**路由级配置**
对 route-a 和 route-b 这两个路由做如下配置:
```yaml
allow:
- consumer1
```
对 *.example.com 和 test.com 在这两个域名做如下配置:
```yaml
allow:
- consumer2
```
若是在控制台进行配置,此例指定的 `route-a``route-b` 即在控制台创建路由时填写的路由名称,当匹配到这两个路由时,将允许`name``consumer1`的调用者访问,其他调用者不允许访问;
此例指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name``consumer2`的调用者访问,其他调用者不允许访问。
#### 根据该配置,下列请求可以允许访问:
**请求指定用户名密码**
```bash
# 假设以下请求将会匹配到route-a路由
# 使用 curl 的 -u 参数指定
curl -u admin:123456 http://xxx.hello.com/test
# 或者直接指定 Authorization 请求头,用户名密码使用 base64 编码
curl -H 'Authorization: Basic YWRtaW46MTIzNDU2' http://xxx.hello.com/test
```
认证鉴权通过后请求的header中会被添加一个`X-Mse-Consumer`字段,在此例中其值为`consumer1`,用以标识调用方的名称
#### 下列请求将拒绝访问:
**请求未提供用户名密码返回401**
```bash
curl http://xxx.hello.com/test
```
**请求提供的用户名密码错误返回401**
```bash
curl -u admin:abc http://xxx.hello.com/test
```
**根据请求的用户名和密码匹配到的调用者无访问权限返回403**
```bash
# consumer2不在route-a的allow列表里
curl -u guest:abc http://xxx.hello.com/test
```
## 相关错误码
| HTTP 状态码 | 出错信息 | 原因说明 |
| ----------- |--------------------------------------------------------------------------------| ---------------------- |
| 401 | Request denied by Basic Auth check. No Basic Authentication information found. | 请求未提供凭证 |
| 401 | Request denied by Basic Auth check. Invalid username and/or password. | 请求凭证无效 |
| 403 | Request denied by Basic Auth check. Unauthorized consumer. | 请求的调用方无访问权限 |

View File

@@ -0,0 +1,119 @@
---
title: Basic Auth
keywords: [higress, basic auth]
description: Basic authentication plug-in configuration reference
---
## 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

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,22 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/basic-auth
go 1.19
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0
github.com/pkg/errors v0.9.1
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
github.com/tidwall/gjson v1.14.3
)
require (
github.com/google/uuid v1.3.0 // 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/wasilibs/nottinygc v0.3.0 // indirect
)

View File

@@ -0,0 +1,9 @@
github.com/WeixinX/higress/plugins/wasm-go v0.0.0-20230911073755-f281286d0cdb/go.mod h1:shD9qvrDS6xklAVjKYho8kHIVdW4A1vhNEOAL2miEEE=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/wasilibs/nottinygc v0.3.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo=

View File

@@ -0,0 +1,330 @@
// 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.
// The 'Basic' HTTP Authentication Scheme: https://datatracker.ietf.org/doc/html/rfc7617
package main
import (
"encoding/base64"
"fmt"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/pkg/errors"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {
wrapper.SetCtx(
"basic-auth",
wrapper.ParseOverrideConfigBy(parseGlobalConfig, parseOverrideRuleConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
// @Name basic-auth
// @Category auth
// @Phase AUTHN
// @Priority 320
// @Title zh-CN Basic Auth
// @Description zh-CN 本插件实现了基于 HTTP Basic Auth 标准进行认证鉴权的功能。
// @Description en-US This plugin implements an authentication function based on HTTP Basic Auth standard.
// @IconUrl https://img.alicdn.com/imgextra/i4/O1CN01BPFGlT1pGZ2VDLgaH_!!6000000005333-2-tps-42-42.png
// @Version 1.0.0
//
// @Contact.name Higress Team
// @Contact.url http://higress.io/
// @Contact.email admin@higress.io
//
// @Example
// global_auth: false
// consumers:
// - name: consumer1
// credential: admin:123456
// - name: consumer2
// credential: guest:abc
//
// @End
type BasicAuthConfig struct {
// @Title 是否开启全局认证
// @Title en-US Enable Global Auth
// @Description 若不开启全局认证,则全局配置只提供凭证信息。只有在域名或路由上进行了配置才会启用认证。
// @Description en-US If set to false, only consumer info will be accepted from the global config. Auth feature shall only be enabled if the corresponding domain or route is configured.
// @Scope GLOBAL
globalAuth *bool `yaml:"global_auth"`
// @Title 调用方列表
// @Title en-US Consumer List
// @Description 服务调用方列表,用于对请求进行认证。
// @Description en-US List of service consumers which will be used in request authentication.
// @Scope GLOBAL
consumers []Consumer `yaml:"consumers"`
// @Title 授权访问的调用方列表
// @Title en-US Allowed Consumers
// @Description 对于匹配上述条件的请求,允许访问的调用方列表。
// @Description en-US Consumers to be allowed for matched requests.
allow []string `yaml:"allow"`
credential2Name map[string]string `yaml:"-"`
username2Passwd map[string]string `yaml:"-"`
}
type Consumer struct {
// @Title 名称
// @Title en-US Name
// @Description 该调用方的名称。
// @Description en-US The name of the consumer.
name string `yaml:"name"`
// @Title 访问凭证
// @Title en-US Credential
// @Description 该调用方的访问凭证。
// @Description en-US The credential of the consumer.
// @Scope GLOBAL
credential string `yaml:"credential"`
}
var (
ruleSet bool // 插件是否至少在一个 domain 或 route 上生效
protectionSpace = "MSE Gateway" // 认证失败时,返回响应头 WWW-Authenticate: Basic realm=MSE Gateway
)
func parseGlobalConfig(json gjson.Result, global *BasicAuthConfig, log wrapper.Log) error {
// log.Debug("global config")
ruleSet = false
global.credential2Name = make(map[string]string)
global.username2Passwd = make(map[string]string)
consumers := json.Get("consumers")
if !consumers.Exists() {
return errors.New("consumers is required")
}
if len(consumers.Array()) == 0 {
return errors.New("consumers cannot be empty")
}
for _, item := range consumers.Array() {
name := item.Get("name")
if !name.Exists() || name.String() == "" {
return errors.New("consumer name is required")
}
credential := item.Get("credential")
if !credential.Exists() || credential.String() == "" {
return errors.New("consumer credential is required")
}
if _, ok := global.credential2Name[credential.String()]; ok {
return errors.Errorf("duplicate consumer credential: %s", credential.String())
}
userAndPasswd := strings.Split(credential.String(), ":")
if len(userAndPasswd) != 2 {
return errors.Errorf("invalid credential format: %s", credential.String())
}
consumer := Consumer{
name: name.String(),
credential: credential.String(),
}
global.consumers = append(global.consumers, consumer)
global.credential2Name[consumer.credential] = consumer.name
global.username2Passwd[userAndPasswd[0]] = userAndPasswd[1]
}
globalAuth := json.Get("global_auth")
if globalAuth.Exists() {
ga := globalAuth.Bool()
global.globalAuth = &ga
}
return nil
}
func parseOverrideRuleConfig(json gjson.Result, global BasicAuthConfig, config *BasicAuthConfig, log wrapper.Log) error {
log.Debug("domain/route config")
// override config via global
*config = global
allow := json.Get("allow")
if !allow.Exists() {
return errors.New("allow is required")
}
if len(allow.Array()) == 0 {
return errors.New("allow cannot be empty")
}
for _, item := range allow.Array() {
config.allow = append(config.allow, item.String())
}
ruleSet = true
return nil
}
// basic-auth 插件认证逻辑:
// - global_auth == true 开启全局生效:
// - 若当前 domain/route 未配置 allow 列表,即未配置该插件:则在所有 consumers 中查找,如果找到则认证通过,否则认证失败 (1*)
// - 若当前 domain/route 配置了该插件:则在 allow 列表中查找,如果找到则认证通过,否则认证失败
//
// - global_auth == false 非全局生效:(2*)
// - 若当前 domain/route 未配置该插件:则直接放行
// - 若当前 domain/route 配置了该插件:则在 allow 列表中查找,如果找到则认证通过,否则认证失败
//
// - global_auth 未设置:
// - 若没有一个 domain/route 配置该插件:则遵循 (1*)
// - 若有至少一个 domain/route 配置该插件:则遵循 (2*)
func onHttpRequestHeaders(ctx wrapper.HttpContext, config BasicAuthConfig, log wrapper.Log) types.Action {
var (
noAllow = len(config.allow) == 0 // 未配置 allow 列表,表示插件在该 domain/route 未生效
globalAuthNoSet = config.globalAuth == nil
globalAuthSetTrue = !globalAuthNoSet && *config.globalAuth
globalAuthSetFalse = !globalAuthNoSet && !*config.globalAuth
)
// log.Debugf("global auth set: %t", !globalAuthNoSet)
// log.Debugf("rule set: %t", ruleSet)
// log.Debugf("config: %+v", config)
// 不需要认证而直接放行的情况:
// - global_auth == false 且 当前 domain/route 未配置该插件
// - global_auth 未设置 且 有至少一个 domain/route 配置该插件 且 当前 domain/route 未配置该插件
if globalAuthSetFalse || (globalAuthNoSet && ruleSet) {
if noAllow {
log.Info("authorization is not required")
return types.ActionContinue
}
}
// 以下为需要认证的情况:
auth, err := proxywasm.GetHttpRequestHeader("Authorization")
if err != nil {
log.Warnf("failed to get authorization: %v", err)
return deniedNoBasicAuthData()
}
if auth == "" {
log.Warnf("authorization is empty")
return deniedNoBasicAuthData()
}
if !strings.HasPrefix(auth, "Basic ") {
log.Warnf("authorization has no prefix 'Basic '")
return deniedNoBasicAuthData()
}
encodedCredential := strings.TrimPrefix(auth, "Basic ")
credentialByte, err := base64.StdEncoding.DecodeString(encodedCredential)
if err != nil {
log.Warnf("failed to decode authorization %q: %v", string(credentialByte), err)
return deniedInvalidCredentials()
}
credential := string(credentialByte)
userAndPasswd := strings.Split(credential, ":")
if len(userAndPasswd) != 2 {
log.Warnf("invalid credential format: %s", credential)
return deniedInvalidCredentials()
}
user, passwd := userAndPasswd[0], userAndPasswd[1]
if correctPasswd, ok := config.username2Passwd[user]; !ok {
log.Warnf("credential username %q is not configured", user)
return deniedInvalidCredentials()
} else {
if passwd != correctPasswd {
log.Warnf("credential password is not correct for username %q", user)
return deniedInvalidCredentials()
}
}
// 以下为 username 和 password 正确的情况:
name, ok := config.credential2Name[credential]
if !ok { // 理论上该分支永远不可达,因为 username 和 password 都是从 credential 中获取的
log.Warnf("credential %q is not configured", credential)
return deniedUnauthorizedConsumer()
}
// 全局生效:
// - global_auth == true 且 当前 domain/route 未配置该插件
// - global_auth 未设置 且 没有任何一个 domain/route 配置该插件
if (globalAuthSetTrue && noAllow) || (globalAuthNoSet && !ruleSet) {
// log.Debug("authenticated case 1")
log.Infof("consumer %q authenticated", name)
return authenticated(name)
}
// 全局生效,但当前 domain/route 配置了 allow 列表
if globalAuthSetTrue && !noAllow {
if !contains(config.allow, name) {
log.Warnf("consumer %q is not allowed", name)
return deniedUnauthorizedConsumer()
}
// log.Debug("authenticated case 2")
log.Infof("consumer %q authenticated", name)
return authenticated(name)
}
// 非全局生效
if globalAuthSetFalse || (globalAuthNoSet && ruleSet) {
if !noAllow { // 配置了 allow 列表
if !contains(config.allow, name) {
log.Warnf("consumer %q is not allowed", name)
return deniedUnauthorizedConsumer()
}
// log.Debug("authenticated case 3")
log.Infof("consumer %q authenticated", name)
return authenticated(name)
}
}
return types.ActionContinue
}
func deniedNoBasicAuthData() types.Action {
_ = proxywasm.SendHttpResponse(401, WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by Basic Auth check. No Basic Authentication information found."), -1)
return types.ActionContinue
}
func deniedInvalidCredentials() types.Action {
_ = proxywasm.SendHttpResponse(401, WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by Basic Auth check. Invalid username and/or password."), -1)
return types.ActionContinue
}
func deniedUnauthorizedConsumer() types.Action {
_ = proxywasm.SendHttpResponse(403, WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by Basic Auth check. Unauthorized consumer."), -1)
return types.ActionContinue
}
func authenticated(name string) types.Action {
_ = proxywasm.AddHttpRequestHeader("X-Mse-Consumer", name)
return types.ActionContinue
}
func WWWAuthenticateHeader(realm string) [][2]string {
return [][2]string{
{"WWW-Authenticate", fmt.Sprintf("Basic realm=%s", realm)},
}
}
func contains(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
}

View File

@@ -0,0 +1,15 @@
# 功能说明
`jwt-auth`插件基于wasm-go实现了Token解析认证功能可以判断Token是否有效如果Token有效则继续访问后端微服务Token无效或不存在直接拒绝并返回401
# 配置字段
| 名称 | 数据类型 | 填写要求 | 描述 |
| ------------ | ------------ | ------------ | ------------ |
| token_secret_key | string | 必填 | 配置Token解析使用的SecretKey|
| token_headers | string | 必填 | 配置获取Token请求头名称|
# 配置示例
```yaml
token_secret_key: Dav7kfq3iA8S!JUj8&CUkdnQe72E@Cw6
token_headers: token
```
此例`token_secret_key`中指定的是认证服务生成Token的SecretKey;`token_headers`是携带Token访问的请求头名称

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,18 @@
module jwt-auth
go 1.19
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230811015533-49269b43032f
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
github.com/tidwall/gjson v1.16.0
)
require (
github.com/google/uuid v1.3.0 // 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/wasilibs/nottinygc v0.3.0 // indirect
)

View File

@@ -0,0 +1,22 @@
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230811015533-49269b43032f h1:H+2fEuroddobcGs2Vom+osc8CE3SBHLz+JbM036Lo9w=
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230811015533-49269b43032f/go.mod h1:shD9qvrDS6xklAVjKYho8kHIVdW4A1vhNEOAL2miEEE=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
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/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.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
github.com/tidwall/gjson v1.16.0/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/wasilibs/nottinygc v0.3.0 h1:0L1jsJ1MsyN5tdinmFbLfuEA0TnHRcqaBM9pDTJVJmU=
github.com/wasilibs/nottinygc v0.3.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -0,0 +1,76 @@
package main
import (
"encoding/json"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
jwt "github.com/dgrijalva/jwt-go"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
// 自定义插件配置
func main() {
wrapper.SetCtx(
"jwt-auth", // 配置插件名称
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type Config struct {
TokenSecretKey string // 解析Token SecretKey
TokenHeaders string // 定义获取Token请求头名称
}
type Res struct {
Code int `json:"code"` // 返回状态码
Msg string `json:"msg"` // 返回信息
}
func parseConfig(json gjson.Result, config *Config, log wrapper.Log) error {
// 解析出配置更新到config中
config.TokenSecretKey = json.Get("token_secret_key").String()
config.TokenHeaders = json.Get("token_headers").String()
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config Config, log wrapper.Log) types.Action {
var res Res
if config.TokenHeaders == "" || config.TokenSecretKey == "" {
res.Code = 401
res.Msg = "参数不足"
data, _ := json.Marshal(res)
_ = proxywasm.SendHttpResponse(401, nil, data, -1)
return types.ActionContinue
}
token, err := proxywasm.GetHttpRequestHeader(config.TokenHeaders)
if err != nil {
res.Code = 401
res.Msg = "认证失败"
data, _ := json.Marshal(res)
_ = proxywasm.SendHttpResponse(401, nil, data, -1)
return types.ActionContinue
}
valid := ParseTokenValid(token, config.TokenSecretKey)
if valid {
_ = proxywasm.ResumeHttpRequest()
return types.ActionPause
} else {
res.Code = 401
res.Msg = "认证失败"
data, _ := json.Marshal(res)
_ = proxywasm.SendHttpResponse(401, nil, data, -1)
return types.ActionContinue
}
}
func ParseTokenValid(tokenString, TokenSecretKey string) bool {
token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// 在这里提供用于验证签名的密钥
return []byte(TokenSecretKey), nil
})
return token.Valid
}

View File

@@ -90,7 +90,8 @@ func (m RuleMatcher[PluginConfig]) GetMatchConfig() (*PluginConfig, error) {
}
func (m *RuleMatcher[PluginConfig]) ParseRuleConfig(config gjson.Result,
parsePluginConfig func(gjson.Result, *PluginConfig) error) error {
parsePluginConfig func(gjson.Result, *PluginConfig) error,
parseOverrideConfig func(gjson.Result, PluginConfig, *PluginConfig) error) error {
var rules []gjson.Result
obj := config.Map()
keyCount := len(obj)
@@ -122,8 +123,15 @@ func (m *RuleMatcher[PluginConfig]) ParseRuleConfig(config gjson.Result,
return fmt.Errorf("parse config failed, no valid rules; global config parse error:%v", globalConfigError)
}
for _, ruleJson := range rules {
var rule RuleConfig[PluginConfig]
err := parsePluginConfig(ruleJson, &rule.config)
var (
rule RuleConfig[PluginConfig]
err error
)
if parseOverrideConfig != nil {
err = parseOverrideConfig(ruleJson, m.globalConfig, &rule.config)
} else {
err = parsePluginConfig(ruleJson, &rule.config)
}
if err != nil {
return err
}

View File

@@ -15,6 +15,7 @@
package matcher
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
@@ -221,7 +222,7 @@ func TestParseRuleConfig(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual RuleMatcher[customConfig]
err := actual.ParseRuleConfig(gjson.Parse(c.config), parseConfig)
err := actual.ParseRuleConfig(gjson.Parse(c.config), parseConfig, nil)
if err != nil {
if c.errMsg == "" {
t.Errorf("parse failed: %v", err)
@@ -236,3 +237,96 @@ func TestParseRuleConfig(t *testing.T) {
})
}
}
type completeConfig struct {
// global config
consumers []string
// rule config
allow []string
}
func parseGlobalConfig(json gjson.Result, global *completeConfig) error {
if json.Get("consumers").Exists() && json.Get("allow").Exists() {
return errors.New("consumers and allow should not be configured at the same level")
}
for _, item := range json.Get("consumers").Array() {
global.consumers = append(global.consumers, item.String())
}
return nil
}
func parseOverrideRuleConfig(json gjson.Result, global completeConfig, config *completeConfig) error {
if json.Get("consumers").Exists() && json.Get("allow").Exists() {
return errors.New("consumers and allow should not be configured at the same level")
}
// override config via global
*config = global
for _, item := range json.Get("allow").Array() {
config.allow = append(config.allow, item.String())
}
return nil
}
func TestParseOverrideConfig(t *testing.T) {
cases := []struct {
name string
config string
errMsg string
expected RuleMatcher[completeConfig]
}{
{
name: "override rule config",
config: `{"consumers":["c1","c2","c3"],"_rules_":[{"_match_route_":["r1","r2"],"allow":["c1","c3"]}]}`,
expected: RuleMatcher[completeConfig]{
ruleConfig: []RuleConfig[completeConfig]{
{
category: Route,
routes: map[string]struct{}{
"r1": {},
"r2": {},
},
config: completeConfig{
consumers: []string{"c1", "c2", "c3"},
allow: []string{"c1", "c3"},
},
},
},
globalConfig: completeConfig{
consumers: []string{"c1", "c2", "c3"},
},
hasGlobalConfig: true,
},
},
{
name: "invalid config",
config: `{"consumers":["c1","c2","c3"],"allow":["c1"]}`,
errMsg: "parse config failed, no valid rules; global config parse error:consumers and allow should not be configured at the same level",
},
{
name: "invalid config",
config: `{"_rules_":[{"_match_route_":["r1","r2"],"consumers":["c1","c2"],"allow":["c1"]}]}`,
errMsg: "consumers and allow should not be configured at the same level",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual RuleMatcher[completeConfig]
err := actual.ParseRuleConfig(gjson.Parse(c.config), parseGlobalConfig, parseOverrideRuleConfig)
if err != nil {
if c.errMsg == "" {
t.Errorf("parse failed: %v", err)
}
if err.Error() != c.errMsg {
t.Errorf("expect err: %s, actual err: %s", c.errMsg, err.Error())
}
return
}
assert.Equal(t, c.expected, actual)
})
}
}

View File

@@ -131,3 +131,23 @@ func (c DnsCluster) ClusterName() string {
func (c DnsCluster) HostName() string {
return c.Domain
}
type ConsulCluster struct {
ServiceName string
Datacenter string
Port int64
Host string
}
func (c ConsulCluster) ClusterName() string {
tail := "consul"
return fmt.Sprintf("outbound|%d||%s.%s.%s",
c.Port, c.ServiceName, c.Datacenter, tail)
}
func (c ConsulCluster) HostName() string {
if c.Host != "" {
return c.Host
}
return c.ServiceName
}

View File

@@ -44,6 +44,7 @@ type HttpContext interface {
}
type ParseConfigFunc[PluginConfig any] func(json gjson.Result, config *PluginConfig, log Log) error
type ParseRuleConfigFunc[PluginConfig any] func(json gjson.Result, global PluginConfig, config *PluginConfig, log Log) error
type onHttpHeadersFunc[PluginConfig any] func(context HttpContext, config PluginConfig, log Log) types.Action
type onHttpBodyFunc[PluginConfig any] func(context HttpContext, config PluginConfig, body []byte, log Log) types.Action
type onHttpStreamDoneFunc[PluginConfig any] func(context HttpContext, config PluginConfig, log Log)
@@ -54,6 +55,7 @@ type CommonVmCtx[PluginConfig any] struct {
log Log
hasCustomConfig bool
parseConfig ParseConfigFunc[PluginConfig]
parseRuleConfig ParseRuleConfigFunc[PluginConfig]
onHttpRequestHeaders onHttpHeadersFunc[PluginConfig]
onHttpRequestBody onHttpBodyFunc[PluginConfig]
onHttpResponseHeaders onHttpHeadersFunc[PluginConfig]
@@ -73,6 +75,13 @@ func ParseConfigBy[PluginConfig any](f ParseConfigFunc[PluginConfig]) SetPluginF
}
}
func ParseOverrideConfigBy[PluginConfig any](f ParseConfigFunc[PluginConfig], g ParseRuleConfigFunc[PluginConfig]) SetPluginFunc[PluginConfig] {
return func(ctx *CommonVmCtx[PluginConfig]) {
ctx.parseConfig = f
ctx.parseRuleConfig = g
}
}
func ProcessRequestHeadersBy[PluginConfig any](f onHttpHeadersFunc[PluginConfig]) SetPluginFunc[PluginConfig] {
return func(ctx *CommonVmCtx[PluginConfig]) {
ctx.onHttpRequestHeaders = f
@@ -161,9 +170,19 @@ func (ctx *CommonPluginCtx[PluginConfig]) OnPluginStart(int) types.OnPluginStart
}
jsonData = gjson.ParseBytes(data)
}
err = ctx.ParseRuleConfig(jsonData, func(js gjson.Result, cfg *PluginConfig) error {
return ctx.vm.parseConfig(js, cfg, ctx.vm.log)
})
var parseOverrideConfig func(gjson.Result, PluginConfig, *PluginConfig) error
if ctx.vm.parseRuleConfig != nil {
parseOverrideConfig = func(js gjson.Result, global PluginConfig, cfg *PluginConfig) error {
return ctx.vm.parseRuleConfig(js, global, cfg, ctx.vm.log)
}
}
err = ctx.ParseRuleConfig(jsonData,
func(js gjson.Result, cfg *PluginConfig) error {
return ctx.vm.parseConfig(js, cfg, ctx.vm.log)
},
parseOverrideConfig,
)
if err != nil {
ctx.vm.log.Warnf("parse rule config failed: %v", err)
return types.OnPluginStartStatusFailed

View File

@@ -58,7 +58,7 @@ func NewReconciler(serviceUpdate func(), client kube.Client, namespace string) *
}
}
func (r *Reconciler) Reconcile(mcpbridge *v1.McpBridge) {
func (r *Reconciler) Reconcile(mcpbridge *v1.McpBridge) error {
newRegistries := make(map[string]*apiv1.RegistryConfig)
if mcpbridge != nil {
for _, registry := range mcpbridge.Spec.Registries {
@@ -121,12 +121,12 @@ func (r *Reconciler) Reconcile(mcpbridge *v1.McpBridge) {
r.registries[k] = v
}
if errHappened {
log.Error("ReconcileRegistries failed, Init Watchers failed")
return
return errors.New("ReconcileRegistries failed, Init Watchers failed")
}
wg.Wait()
r.Cache.PurgeStaleService()
log.Infof("Registries is reconciled")
return nil
}
func (r *Reconciler) generateWatcherFromRegistryConfig(registry *apiv1.RegistryConfig, wg *sync.WaitGroup) (Watcher, error) {

View File

@@ -9,11 +9,11 @@ Higress e2e tests are mainly focusing on two parts for now:
### Architecture
![ingress-arch](./ingress/arch.png)
![ingress-arch](./e2e/arch.png)
### Workflow
![ingress-workflow](./ingress/pipeline.png)
![ingress-workflow](./e2e/pipeline.png)
Higress provides make target to run ingress api conformance tests and wasmplugin tests,

View File

@@ -0,0 +1,119 @@
# 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.
# This file contains the base resources that most conformance tests will rely
# on. This includes 3 namespaces along with Gateways, Services and Deployments
# that can be used as backends for routing traffic. The most important
# resources included are the Gateways (all in the higress-conformance-infra
# namespace):
# - same-namespace (only supports route in same ns)
# - all-namespaces (supports routes in all ns)
# - backend-namespaces (supports routes in ns with backend label)
apiVersion: v1
kind: Pod
metadata:
name: consul-standlone
namespace: higress-conformance-app-backend
labels:
name: consul-standlone
spec:
containers:
- name: consul
image: docker.io/hashicorp/consul:1.16.0
resources:
requests:
cpu: 10m
ports:
- containerPort: 8500
name: http
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: consul-service
namespace: higress-conformance-app-backend
labels:
name: consul-standlone
spec:
clusterIP: None
ports:
- name: http-query
port: 8500
protocol: TCP
targetPort: 8500
selector:
name: consul-standlone
---
apiVersion: v1
kind: Pod
metadata:
name: httpbin
namespace: higress-conformance-app-backend
spec:
containers:
- name: httpbin
image: registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin:1.0.2
command:
- /app/httpbin
- --registry-type=consul
- --consul-server-address=consul-service:8500
- --server-port=8080
- --service-tags=higress,httpbin
env:
- name: SERVICE_NAME
value: httpbin
- name: VERSION
value: v1
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 5
httpGet:
path: /ping
port: 8080
scheme: HTTP
periodSeconds: 20
successThreshold: 1
timeoutSeconds: 1
livenessProbe:
httpGet:
path: /ping
port: 8080
scheme: HTTP
initialDelaySeconds: 20
periodSeconds: 20

View File

@@ -0,0 +1,39 @@
# 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.
# This file contains the base resources that most conformance tests will rely
# on. This includes 3 namespaces along with Gateways, Services and Deployments
# that can be used as backends for routing traffic. The most important
# resources included are the Gateways (all in the higress-conformance-infra
# namespace):
# - same-namespace (only supports route in same ns)
# - all-namespaces (supports routes in all ns)
# - backend-namespaces (supports routes in ns with backend label)
apiVersion: v1
kind: Pod
metadata:
name: dubbo-demo-provider
namespace: higress-conformance-app-backend
spec:
containers:
- name: dubbo-demo-provider
image: registry.cn-hangzhou.aliyuncs.com/hinsteny/dubbo-provider-demo:0.0.2
env:
- name: NACOS_K8S_NAMESPACE
value: higress-conformance-app-backend
- name: DUBBO_GROUP
value: dev
ports:
- containerPort: 20880

View File

@@ -0,0 +1,125 @@
# 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.
# This file contains the base resources that most conformance tests will rely
# on. This includes 3 namespaces along with Gateways, Services and Deployments
# that can be used as backends for routing traffic. The most important
# resources included are the Gateways (all in the higress-conformance-infra
# namespace):
# - same-namespace (only supports route in same ns)
# - all-namespaces (supports routes in all ns)
# - backend-namespaces (supports routes in ns with backend label)
apiVersion: v1
kind: ConfigMap
metadata:
name: eureka-cm
namespace: higress-conformance-app-backend
data:
# if you want to deploy n instances of eureka cluster,
# you should set eureka_service_address: http://eureka-0.eureka:8761/eureka,...,http://eureka-(n-1).eureka:8761/eureka
eureka_service_address: "http://eureka-0.eureka.higress-conformance-app-backend.svc:8761/eureka"
---
apiVersion: v1
kind: Service
metadata:
name: eureka
namespace: higress-conformance-app-backend
labels:
app: eureka
spec:
clusterIP: None
ports:
- port: 8761
name: eureka
selector:
app: eureka
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: eureka
namespace: higress-conformance-app-backend
spec:
serviceName: eureka
# n instances
replicas: 1
selector:
matchLabels:
app: eureka
template:
metadata:
labels:
app: eureka
spec:
containers:
- name: eureka
image: bitinit/eureka
ports:
- containerPort: 8761
name: http
protocol: TCP
resources:
requests:
memory: "500Mi"
cpu: "500m"
env:
- name: EUREKA_SERVER_ADDRESS
valueFrom:
configMapKeyRef:
name: eureka-cm
key: eureka_service_address
- name: ENVIRONMENT
value: "prod"
- name: JVM_OPTS
value: "-Xms1g -Xmx1g"
livenessProbe:
httpGet:
path: /
port: 8761
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8761
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Pod
metadata:
name: eureka-registry-provider
namespace: higress-conformance-app-backend
spec:
containers:
- name: eureka-registry-provider
image: charlie1380/eureka-registry-provider:v0.3.0
env:
- name: EUREKA_SERVER_ADDRESS
valueFrom:
configMapKeyRef:
name: eureka-cm
key: eureka_service_address
ports:
- containerPort: 8888
name: http
protocol: TCP
readinessProbe:
failureThreshold: 5
httpGet:
path: /healthz
port: 8888
scheme: HTTP
periodSeconds: 1

View File

@@ -318,250 +318,3 @@ spec:
resources:
requests:
cpu: 10m
---
apiVersion: v1
kind: Pod
metadata:
name: nacos-standlone-rc3
namespace: higress-conformance-app-backend
labels:
name: nacos-standlone-rc3
spec:
containers:
- name: nacos-standlone-rc3
image: registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3:1.0.0-RC3
ports:
- containerPort: 8848
---
apiVersion: v1
kind: Service
metadata:
name: nacos-standlone-rc3-service
namespace: higress-conformance-app-backend
spec:
selector:
name: nacos-standlone-rc3
clusterIP: None
ports:
- name: foo # name is not required for single-port Services
port: 8848
---
apiVersion: v1
kind: Pod
metadata:
name: dubbo-demo-provider
namespace: higress-conformance-app-backend
spec:
containers:
- name: dubbo-demo-provider
image: registry.cn-hangzhou.aliyuncs.com/hinsteny/dubbo-provider-demo:0.0.2
env:
- name: NACOS_K8S_NAMESPACE
value: higress-conformance-app-backend
- name: DUBBO_GROUP
value: dev
ports:
- containerPort: 20880
---
apiVersion: v1
kind: Pod
metadata:
name: consul-standlone
namespace: higress-conformance-app-backend
labels:
name: consul-standlone
spec:
containers:
- name: consul
image: docker.io/hashicorp/consul:1.16.0
resources:
requests:
cpu: 10m
ports:
- containerPort: 8500
name: http
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: consul-service
namespace: higress-conformance-app-backend
labels:
name: consul-standlone
spec:
clusterIP: None
ports:
- name: http-query
port: 8500
protocol: TCP
targetPort: 8500
selector:
name: consul-standlone
---
apiVersion: v1
kind: Pod
metadata:
name: httpbin
namespace: higress-conformance-app-backend
spec:
containers:
- name: httpbin
image: registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin:1.0.2
command:
- /app/httpbin
- --registry-type=consul
- --consul-server-address=consul-service:8500
- --server-port=8080
- --service-tags=higress,httpbin
env:
- name: SERVICE_NAME
value: httpbin
- name: VERSION
value: v1
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 5
httpGet:
path: /ping
port: 8080
scheme: HTTP
periodSeconds: 20
successThreshold: 1
timeoutSeconds: 1
livenessProbe:
httpGet:
path: /ping
port: 8080
scheme: HTTP
initialDelaySeconds: 20
periodSeconds: 20
---
# Eureka service registry
---
apiVersion: v1
kind: ConfigMap
metadata:
name: eureka-cm
namespace: higress-conformance-app-backend
data:
# if you want to deploy n instances of eureka cluster,
# you should set eureka_service_address: http://eureka-0.eureka:8761/eureka,...,http://eureka-(n-1).eureka:8761/eureka
eureka_service_address: "http://eureka-0.eureka.higress-conformance-app-backend.svc:8761/eureka"
---
apiVersion: v1
kind: Service
metadata:
name: eureka
namespace: higress-conformance-app-backend
labels:
app: eureka
spec:
clusterIP: None
ports:
- port: 8761
name: eureka
selector:
app: eureka
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: eureka
namespace: higress-conformance-app-backend
spec:
serviceName: eureka
# n instances
replicas: 1
selector:
matchLabels:
app: eureka
template:
metadata:
labels:
app: eureka
spec:
containers:
- name: eureka
image: bitinit/eureka
ports:
- containerPort: 8761
name: http
protocol: TCP
resources:
requests:
memory: "500Mi"
cpu: "500m"
env:
- name: EUREKA_SERVER_ADDRESS
valueFrom:
configMapKeyRef:
name: eureka-cm
key: eureka_service_address
- name: ENVIRONMENT
value: "prod"
- name: JVM_OPTS
value: "-Xms1g -Xmx1g"
livenessProbe:
httpGet:
path: /
port: 8761
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8761
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Pod
metadata:
name: eureka-registry-provider
namespace: higress-conformance-app-backend
spec:
containers:
- name: eureka-registry-provider
image: charlie1380/eureka-registry-provider:v0.3.0
env:
- name: EUREKA_SERVER_ADDRESS
valueFrom:
configMapKeyRef:
name: eureka-cm
key: eureka_service_address
ports:
- containerPort: 8888
name: http
protocol: TCP
readinessProbe:
failureThreshold: 5
httpGet:
path: /healthz
port: 8888
scheme: HTTP
periodSeconds: 1

View File

@@ -0,0 +1,49 @@
# 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.
# This file contains the base resources that most conformance tests will rely
# on. This includes 3 namespaces along with Gateways, Services and Deployments
# that can be used as backends for routing traffic. The most important
# resources included are the Gateways (all in the higress-conformance-infra
# namespace):
# - same-namespace (only supports route in same ns)
# - all-namespaces (supports routes in all ns)
# - backend-namespaces (supports routes in ns with backend label)
apiVersion: v1
kind: Pod
metadata:
name: nacos-standlone-rc3
namespace: higress-conformance-app-backend
labels:
name: nacos-standlone-rc3
spec:
containers:
- name: nacos-standlone-rc3
image: registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3:1.0.0-RC3
ports:
- containerPort: 8848
---
apiVersion: v1
kind: Service
metadata:
name: nacos-standlone-rc3-service
namespace: higress-conformance-app-backend
spec:
selector:
name: nacos-standlone-rc3
clusterIP: None
ports:
- name: foo # name is not required for single-port Services
port: 8848

View File

@@ -0,0 +1,133 @@
// 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/e2e/conformance/utils/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
)
func init() {
Register(CPPWasmPluginsBasicAuth)
}
var CPPWasmPluginsBasicAuth = suite.ConformanceTest{
ShortName: "CPPWasmPluginsBasicAuth",
Description: "The Ingress in the higress-conformance-infra namespace test the CPP basic-auth WASM plugin.",
Manifests: []string{"tests/cpp-wasm-basic-auth.yaml"},
Features: []suite.SupportedFeature{suite.WASMCPPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TestCaseName: "case 1: Successful authentication",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"Authorization": "Basic YWRtaW46MTIzNDU2"}, // base64("admin:123456")
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"X-Mse-Consumer": "consumer1"},
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
{
Meta: http.AssertionMeta{
TestCaseName: "case 2: No Basic Authentication information found",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
AdditionalResponseHeaders: map[string]string{
"WWW-Authenticate": "Basic realm=MSE Gateway",
},
},
},
{
Meta: http.AssertionMeta{
TestCaseName: "case 3: Invalid username and/or password",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"Authorization": "Basic YWRtaW46cXdlcg=="}, // base64("admin:qwer")
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
AdditionalResponseHeaders: map[string]string{
"WWW-Authenticate": "Basic realm=MSE Gateway",
},
},
},
{
Meta: http.AssertionMeta{
TestCaseName: "case 4: Unauthorized consumer",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"Authorization": "Basic Z3Vlc3Q6YWJj"}, // base64("guest:abc")
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
AdditionalResponseHeaders: map[string]string{
"WWW-Authenticate": "Basic realm=MSE Gateway",
},
},
},
}
t.Run("WasmPlugins CPP basic-auth", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,56 @@
# 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:
name: wasmplugin-cpp-basic-auth
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Prefix
path: "/foo"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: cpp-basic-auth
namespace: higress-system
spec:
defaultConfig:
consumers:
- credential: admin:123456
name: consumer1
- credential: guest:abc
name: consumer2
global_auth: false
defaultConfigDisable: false
matchRules:
- config:
allow:
- consumer1
configDisable: false
ingress:
- higress-conformance-infra/wasmplugin-cpp-basic-auth
url: file:///opt/plugins/wasm-cpp/extensions/basic-auth/plugin.wasm

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, CPPWasmPluginsKeyAuth)
Register(CPPWasmPluginsKeyAuth)
}
var CPPWasmPluginsKeyAuth = suite.ConformanceTest{
ShortName: "CPPWasmPluginsKeyAuth",
Description: "The Ingress in the higress-conformance-infra namespace test the CPP key_auth wasmplugins.",
Manifests: []string{"tests/cpp-key_auth.yaml"},
Manifests: []string{"tests/cpp-wasm-key-auth.yaml"},
Features: []suite.SupportedFeature{suite.WASMCPPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, CPPWasmPluginsRequestBlock)
Register(CPPWasmPluginsRequestBlock)
}
var CPPWasmPluginsRequestBlock = suite.ConformanceTest{
ShortName: "CPPWasmPluginsRequestBlock",
Description: "The Ingress in the higress-conformance-infra namespace test the cpp request-block wasmplugins.",
Manifests: []string{"tests/cpp-request_block.yaml"},
Manifests: []string{"tests/cpp-wasm-request-block.yaml"},
Features: []suite.SupportedFeature{suite.WASMCPPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -0,0 +1,133 @@
// 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/e2e/conformance/utils/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
)
func init() {
Register(WasmPluginsBasicAuth)
}
var WasmPluginsBasicAuth = suite.ConformanceTest{
ShortName: "WasmPluginsBasicAuth",
Description: "The Ingress in the higress-conformance-infra namespace test the basic-auth WASM plugin.",
Manifests: []string{"tests/go-wasm-basic-auth.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TestCaseName: "case 1: Successful authentication",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"Authorization": "Basic YWRtaW46MTIzNDU2"}, // base64("admin:123456")
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"X-Mse-Consumer": "consumer1"},
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
{
Meta: http.AssertionMeta{
TestCaseName: "case 2: No Basic Authentication information found",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
AdditionalResponseHeaders: map[string]string{
"WWW-Authenticate": "Basic realm=MSE Gateway",
},
},
},
{
Meta: http.AssertionMeta{
TestCaseName: "case 3: Invalid username and/or password",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"Authorization": "Basic YWRtaW46cXdlcg=="}, // base64("admin:qwer")
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
AdditionalResponseHeaders: map[string]string{
"WWW-Authenticate": "Basic realm=MSE Gateway",
},
},
},
{
Meta: http.AssertionMeta{
TestCaseName: "case 4: Unauthorized consumer",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"Authorization": "Basic Z3Vlc3Q6YWJj"}, // base64("guest:abc")
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
AdditionalResponseHeaders: map[string]string{
"WWW-Authenticate": "Basic realm=MSE Gateway",
},
},
},
}
t.Run("WasmPlugins basic-auth", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,56 @@
# 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:
name: wasmplugin-basic-auth
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Prefix
path: "/foo"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: basic-auth
namespace: higress-system
spec:
defaultConfig:
consumers:
- credential: admin:123456
name: consumer1
- credential: guest:abc
name: consumer2
global_auth: false
defaultConfigDisable: false
matchRules:
- config:
allow:
- consumer1
configDisable: false
ingress:
- higress-conformance-infra/wasmplugin-basic-auth
url: file:///opt/plugins/wasm-go/extensions/basic-auth/plugin.wasm

View File

@@ -0,0 +1,60 @@
// 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/e2e/conformance/utils/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
)
func init() {
Register(WasmPluginsJwtAuth)
}
var WasmPluginsJwtAuth = suite.ConformanceTest{
ShortName: "WasmPluginsJwtAuth",
Description: "The Ingress in the higress-conformance-infra namespace test the jwt-auth wasmplugins.",
Manifests: []string{"tests/go-wasm-jwt-auth.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
},
},
}
t.Run("WasmPlugins jwt-auth", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,43 @@
# 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:
name: httproute-app-root
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: jwt-auth
namespace: higress-system
spec:
defaultConfig:
token_headers: token
token_secret_key: Dav7kfq3iA8S!JUj8&CUkdnQe72E@Cw6
url: file:///opt/plugins/wasm-go/extensions/jwt-auth/plugin.wasm

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, WasmPluginsRequestBlock)
Register(WasmPluginsRequestBlock)
}
var WasmPluginsRequestBlock = suite.ConformanceTest{
ShortName: "WasmPluginsRequestBlock",
Description: "The Ingress in the higress-conformance-infra namespace test the request-block wasmplugins.",
Manifests: []string{"tests/request-block.yaml"},
Manifests: []string{"tests/go-wasm-request-block.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -23,13 +23,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteAppRoot)
Register(HTTPRouteAppRoot)
}
var HTTPRouteAppRoot = suite.ConformanceTest{
ShortName: "HTTPRouteAppRoot",
Description: "The Ingress in the higress-conformance-infra namespace uses the app root.",
Manifests: []string{"tests/httproute-app-root.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteCanaryHeaderWithCustomizedHeader)
Register(HTTPRouteCanaryHeaderWithCustomizedHeader)
}
var HTTPRouteCanaryHeaderWithCustomizedHeader = suite.ConformanceTest{
ShortName: "HTTPRouteCanaryHeaderWithCustomizedHeader",
Description: "The Ingress in the higress-conformance-infra namespace uses the canary header traffic split when same host and path but different header",
Manifests: []string{"tests/httproute-canary-header-with-customized-header.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteCanaryHeader)
Register(HTTPRouteCanaryHeader)
}
var HTTPRouteCanaryHeader = suite.ConformanceTest{
ShortName: "HTTPRouteCanaryHeader",
Description: "The Ingress in the higress-conformance-infra namespace uses the canary header traffic split.",
Manifests: []string{"tests/httproute-canary-header.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteCanaryWeight)
Register(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"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
// test if the weight is 0

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteConsulHttpBin)
Register(HTTPRouteConsulHttpBin)
}
var HTTPRouteConsulHttpBin = suite.ConformanceTest{
ShortName: "HTTPRouteConsulHttpBin",
Description: "The Ingress in the higress-conformance-infra namespace uses the consul service registry.",
Manifests: []string{"tests/httproute-consul-httpbin.yaml"},
Features: []suite.SupportedFeature{suite.ConsulConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -27,13 +27,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteDownstreamEncryption)
Register(HTTPRouteDownstreamEncryption)
}
var HTTPRouteDownstreamEncryption = suite.ConformanceTest{
ShortName: "HTTPRouteDownstreamEncryption",
Description: "A single Ingress in the higress-conformance-infra namespace for downstream encryption.",
Manifests: []string{"tests/httproute-downstream-encryption.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
// Prepare certificates and secrets for testcases
caCertOut, _, caCert, caKey := cert.MustGenerateCaCert(t)

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteEnableCors)
Register(HTTPRouteEnableCors)
}
var HTTPRouteEnableCors = suite.ConformanceTest{
ShortName: "HTTPRouteEnableCors",
Description: "A single Ingress in the higress-conformance-infra namespace demonstrates enable cors ability.",
Manifests: []string{"tests/httproute-enable-cors.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteEnableIgnoreCase)
Register(HTTPRouteEnableIgnoreCase)
}
var HTTPRouteEnableIgnoreCase = suite.ConformanceTest{
ShortName: "HTTPRouteEnableIgnoreCase",
Description: "A Ingress in the higress-conformance-infra namespace that ignores URI case in HTTP match.",
Manifests: []string{"tests/httproute-enable-ignore-case.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -23,13 +23,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteEurekaRegistry)
Register(HTTPRouteEurekaRegistry)
}
var HTTPRouteEurekaRegistry = suite.ConformanceTest{
ShortName: "HTTPRouteEurekaRegistry",
Description: "The Ingress in the higress-conformance-infra namespace uses the eureka service registry.",
Manifests: []string{"tests/httproute-eureka-registry.yaml"},
Features: []suite.SupportedFeature{suite.EurekaConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -23,13 +23,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HttpForceRedirectHttps)
Register(HttpForceRedirectHttps)
}
var HttpForceRedirectHttps = suite.ConformanceTest{
ShortName: "HttpForceRedirectHttps",
Description: " The ingress in the higress-conformance-infra namespace enforces server-side HTTPS with forced redirection.",
Manifests: []string{"tests/httproute-force-redirect-https.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteFullPathRegex)
Register(HTTPRouteFullPathRegex)
}
var HTTPRouteFullPathRegex = suite.ConformanceTest{
ShortName: "HTTPRouteFullPathRegex",
Description: "test for 'higress.io/full-path-regex' annotation",
Manifests: []string{"tests/httproute-full-path-regex.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testCases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteHostNameSameNamespace)
Register(HTTPRouteHostNameSameNamespace)
}
var HTTPRouteHostNameSameNamespace = suite.ConformanceTest{
ShortName: "HTTPRouteHostNameSameNamespace",
Description: "A Ingress in the higress-conformance-infra namespace demonstrates host match ability.",
Manifests: []string{"tests/httproute-hostname-same-namespace.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteHttp2Rpc)
Register(HTTPRouteHttp2Rpc)
}
var HTTPRouteHttp2Rpc = suite.ConformanceTest{
ShortName: "HTTPRouteHttp2Rpc",
Description: "The Ingress in the higress-conformance-infra namespace uses the http2rpc.",
Manifests: []string{"tests/httproute-http2rpc.yaml"},
Features: []suite.SupportedFeature{suite.DubboConformanceFeature, suite.NacosConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
@@ -47,7 +48,7 @@ var HTTPRouteHttp2Rpc = suite.ConformanceTest{
},
},
}
t.Run("HTTPRoute app root", func(t *testing.T) {
t.Run("HTTPRoute uses HTTP to RPC", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteMatchHeaders)
Register(HTTPRouteMatchHeaders)
}
var HTTPRouteMatchHeaders = suite.ConformanceTest{
ShortName: "HTTPRouteMatchHeaders",
Description: "A single Ingress in the higress-conformance-infra namespace uses the match headers.",
Manifests: []string{"tests/httproute-match-headers.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteMatchMethods)
Register(HTTPRouteMatchMethods)
}
var HTTPRouteMatchMethods = suite.ConformanceTest{
ShortName: "HTTPRouteMatchMethods",
Description: "A single Ingress in the higress-conformance-infra namespace uses the match methods.",
Manifests: []string{"tests/httproute-match-methods.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteMatchPath)
Register(HTTPRouteMatchPath)
}
var HTTPRouteMatchPath = suite.ConformanceTest{
ShortName: "HTTPRouteMatchPath",
Description: "A Ingress in the higress-conformance-infra namespace that match different path.",
Manifests: []string{"tests/httproute-match-path.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteMatchQueryParams)
Register(HTTPRouteMatchQueryParams)
}
var HTTPRouteMatchQueryParams = suite.ConformanceTest{
ShortName: "HTTPRouteMatchQueryParams",
Description: "A single Ingress in the higress-conformance-infra namespace uses the match queryParams.",
Manifests: []string{"tests/httproute-match-query-params.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -23,13 +23,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRoutePermanentRedirectCode)
Register(HTTPRoutePermanentRedirectCode)
}
var HTTPRoutePermanentRedirectCode = suite.ConformanceTest{
ShortName: "HTTPRoutePermanentRedirectCode",
Description: "The Ingress in the higress-conformance-infra namespace uses the permanent redirect code header.",
Manifests: []string{"tests/httproute-permanent-redirect-code.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -23,13 +23,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRoutePermanentRedirect)
Register(HTTPRoutePermanentRedirect)
}
var HTTPRoutePermanentRedirect = suite.ConformanceTest{
ShortName: "HTTPRoutePermanentRedirect",
Description: "The Ingress in the higress-conformance-infra namespace uses the permanent redirect header.",
Manifests: []string{"tests/httproute-permanent-redirect.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -23,13 +23,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HttpRedirectAsHttps)
Register(HttpRedirectAsHttps)
}
var HttpRedirectAsHttps = suite.ConformanceTest{
ShortName: "HttpRedirectAsHttps",
Description: "The Ingress in the higress-conformance-infra namespace Server-side HTTPS enforcement through redirect.",
Manifests: []string{"tests/httproute-redirct-as-https.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteRequestHeaderControl)
Register(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"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteRewriteHost)
Register(HTTPRouteRewriteHost)
}
var HTTPRouteRewriteHost = suite.ConformanceTest{
ShortName: "HTTPRouteRewriteHost",
Description: "A single Ingress in the higress-conformance-infra namespace uses the rewrite host.",
Manifests: []string{"tests/httproute-rewrite-host.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteRewritePath)
Register(HTTPRouteRewritePath)
}
var HTTPRouteRewritePath = suite.ConformanceTest{
ShortName: "HTTPRouteRewritePath",
Description: "A single Ingress in the higress-conformance-infra namespace uses the rewrite path.",
Manifests: []string{"tests/httproute-rewrite-path.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testCases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteSameHostAndPath)
Register(HTTPRouteSameHostAndPath)
}
var HTTPRouteSameHostAndPath = suite.ConformanceTest{
ShortName: "HTTPRouteSameHostAndPath",
Description: "A single Ingress in the higress-conformance-infra namespace demonstrates the situation with same path and host",
Manifests: []string{"tests/httproute-same-host-and-path.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteSimpleSameNamespace)
Register(HTTPRouteSimpleSameNamespace)
}
var HTTPRouteSimpleSameNamespace = suite.ConformanceTest{
ShortName: "HTTPRouteSimpleSameNamespace",
Description: "A single Ingress in the higress-conformance-infra namespace demonstrates basic routing ability.",
Manifests: []string{"tests/httproute-simple-same-namespace.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
t.Run("Simple HTTP request should reach infra-backend", func(t *testing.T) {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.Assertion{

View File

@@ -23,13 +23,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteTemporalRedirect)
Register(HTTPRouteTemporalRedirect)
}
var HTTPRouteTemporalRedirect = suite.ConformanceTest{
ShortName: "HTTPRouteTemporalRedirect",
Description: "The Ingress in the higress-conformance-infra namespace uses the temporal redirect header.",
Manifests: []string{"tests/httproute-temporal-redirect.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -22,13 +22,14 @@ import (
)
func init() {
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteWhitelistSourceRange)
Register(HTTPRouteWhitelistSourceRange)
}
var HTTPRouteWhitelistSourceRange = suite.ConformanceTest{
ShortName: "HTTPRouteWhitelistSourceRange",
Description: "A single Ingress in the higress-conformance-infra namespace demonstrates ip access control",
Manifests: []string{"tests/httproute-whitelist-source-range.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{

View File

@@ -16,4 +16,11 @@ package tests
import "github.com/alibaba/higress/test/e2e/conformance/utils/suite"
var HigressConformanceTests []suite.ConformanceTest
func Register(testcase suite.ConformanceTest) {
if len(testcase.Features) == 0 {
panic("must set at least one feature of the test case")
}
ConformanceTests = append(ConformanceTests, testcase)
}
var ConformanceTests []suite.ConformanceTest

View File

@@ -23,4 +23,7 @@ var (
CleanupBaseResources = flag.Bool("cleanup-base-resources", true, "Whether to cleanup base test resources after the run")
SupportedFeatures = flag.String("supported-features", "", "Supported features included in conformance tests suites")
ExemptFeatures = flag.String("exempt-features", "", "Exempt Features excluded from conformance tests suites")
IsWasmPluginTest = flag.Bool("isWasmPluginTest", false, "Determine if run wasm plugin conformance test")
WasmPluginType = flag.String("wasmPluginType", "GO", "Define wasm plugin type, currently supports GO, CPP")
WasmPluginName = flag.String("wasmPluginName", "", "Define wasm plugin name")
)

View File

@@ -97,7 +97,7 @@ func (a Applier) MustApplyObjectsWithCleanup(t *testing.T, c client.Client, time
ctx, cancel := context.WithTimeout(context.Background(), timeoutConfig.CreateTimeout)
defer cancel()
t.Logf("Creating %s %s", resource.GetName(), resource.GetObjectKind().GroupVersionKind().Kind)
t.Logf("🏗 Creating %s %s", resource.GetName(), resource.GetObjectKind().GroupVersionKind().Kind)
err := c.Create(ctx, resource)
if err != nil {
@@ -110,7 +110,7 @@ func (a Applier) MustApplyObjectsWithCleanup(t *testing.T, c client.Client, time
t.Cleanup(func() {
ctx, cancel = context.WithTimeout(context.Background(), timeoutConfig.DeleteTimeout)
defer cancel()
t.Logf("Deleting %s %s", resource.GetName(), resource.GetObjectKind().GroupVersionKind().Kind)
t.Logf("🚮 Deleting %s %s", resource.GetName(), resource.GetObjectKind().GroupVersionKind().Kind)
err = c.Delete(ctx, resource)
require.NoErrorf(t, err, "error deleting resource")
})
@@ -129,7 +129,7 @@ func (a Applier) MustApplyWithCleanup(t *testing.T, c client.Client, timeoutConf
resources, err := a.prepareResources(t, decoder)
if err != nil {
t.Logf("manifest: %s", data.String())
t.Logf("🧳 Manifest: %s", data.String())
require.NoErrorf(t, err, "error parsing manifest")
}
@@ -146,7 +146,7 @@ func (a Applier) MustApplyWithCleanup(t *testing.T, c client.Client, timeoutConf
if !apierrors.IsNotFound(err) {
require.NoErrorf(t, err, "error getting resource")
}
t.Logf("Creating %s %s", uObj.GetName(), uObj.GetKind())
t.Logf("🏗 Creating %s %s", uObj.GetName(), uObj.GetKind())
err = c.Create(ctx, uObj)
require.NoErrorf(t, err, "error creating resource")
@@ -154,7 +154,7 @@ func (a Applier) MustApplyWithCleanup(t *testing.T, c client.Client, timeoutConf
t.Cleanup(func() {
ctx, cancel = context.WithTimeout(context.Background(), timeoutConfig.DeleteTimeout)
defer cancel()
t.Logf("Deleting %s %s", uObj.GetName(), uObj.GetKind())
t.Logf("🚮 Deleting %s %s", uObj.GetName(), uObj.GetKind())
err = c.Delete(ctx, uObj)
require.NoErrorf(t, err, "error deleting resource")
})
@@ -163,14 +163,14 @@ func (a Applier) MustApplyWithCleanup(t *testing.T, c client.Client, timeoutConf
}
uObj.SetResourceVersion(fetchedObj.GetResourceVersion())
t.Logf("Updating %s %s", uObj.GetName(), uObj.GetKind())
t.Logf("🏗 Updating %s %s", uObj.GetName(), uObj.GetKind())
err = c.Update(ctx, uObj)
if cleanup {
t.Cleanup(func() {
ctx, cancel = context.WithTimeout(context.Background(), timeoutConfig.DeleteTimeout)
defer cancel()
t.Logf("Deleting %s %s", uObj.GetName(), uObj.GetKind())
t.Logf("🚮 Deleting %s %s", uObj.GetName(), uObj.GetKind())
err = c.Delete(ctx, uObj)
require.NoErrorf(t, err, "error deleting resource")
})

View File

@@ -54,7 +54,7 @@ func NamespacesMustBeAccepted(t *testing.T, c client.Client, timeoutConfig confi
podList := &v1.PodList{}
err := c.List(ctx, podList, client.InNamespace(ns))
if err != nil {
t.Errorf("Error listing Pods: %v", err)
t.Errorf("Error listing Pods: %v", err)
}
for _, pod := range podList.Items {
if !FindPodConditionInList(t, pod.Status.Conditions, "Ready", "True") &&
@@ -64,7 +64,7 @@ func NamespacesMustBeAccepted(t *testing.T, c client.Client, timeoutConfig confi
}
}
}
t.Logf("Gateways and Pods in %s namespaces ready", strings.Join(namespaces, ", "))
t.Logf("Gateways and Pods in %s namespaces ready", strings.Join(namespaces, ", "))
return true, nil
})
require.NoErrorf(t, waitErr, "error waiting for %s namespaces to be ready", strings.Join(namespaces, ", "))
@@ -72,7 +72,7 @@ func NamespacesMustBeAccepted(t *testing.T, c client.Client, timeoutConfig confi
func ConditionsMatch(t *testing.T, expected, actual []metav1.Condition) bool {
if len(actual) < len(expected) {
t.Logf("Expected more conditions to be present")
t.Logf("⌛️ Expected more conditions to be present")
return false
}
for _, condition := range expected {
@@ -81,7 +81,7 @@ func ConditionsMatch(t *testing.T, expected, actual []metav1.Condition) bool {
}
}
t.Logf("Conditions matched expectations")
t.Logf("Conditions matched expectations")
return true
}
@@ -95,14 +95,14 @@ func FindConditionInList(t *testing.T, conditions []metav1.Condition, condName,
if expectedReason == "" || cond.Reason == expectedReason {
return true
}
t.Logf("%s condition Reason set to %s, expected %s", condName, cond.Reason, expectedReason)
t.Logf("⌛️ %s condition Reason set to %s, expected %s", condName, cond.Reason, expectedReason)
}
t.Logf("%s condition set to Status %s with Reason %v, expected Status %s", condName, cond.Status, cond.Reason, expectedStatus)
t.Logf("⌛️ %s condition set to Status %s with Reason %v, expected Status %s", condName, cond.Status, cond.Reason, expectedStatus)
}
}
t.Logf("%s was not in conditions list", condName)
t.Logf("⌛️ %s was not in conditions list", condName)
return false
}
@@ -112,10 +112,10 @@ func FindPodConditionInList(t *testing.T, conditions []v1.PodCondition, condName
if cond.Status == v1.ConditionStatus(condValue) {
return true
}
t.Logf("%s condition set to %s, expected %s", condName, cond.Status, condValue)
t.Logf("⌛️ %s condition set to %s, expected %s", condName, cond.Status, condValue)
}
}
t.Logf("%s was not in conditions list", condName)
t.Logf("⌛️ %s was not in conditions list", condName)
return false
}

View File

@@ -0,0 +1,45 @@
// 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 suite
import "istio.io/istio/pilot/pkg/util/sets"
type SupportedFeature string
const (
// core: http
HTTPConformanceFeature SupportedFeature = "http"
// extended: extensibility
WASMGoConformanceFeature SupportedFeature = "wasm-go"
WASMCPPConformanceFeature SupportedFeature = "wasm-cpp"
// extended: service discovery
DubboConformanceFeature SupportedFeature = "dubbo"
EurekaConformanceFeature SupportedFeature = "eureka"
ConsulConformanceFeature SupportedFeature = "consul"
NacosConformanceFeature SupportedFeature = "nacos"
)
var AllFeatures = sets.Set{}.
Insert(string(HTTPConformanceFeature)).
Insert(string(DubboConformanceFeature)).
Insert(string(EurekaConformanceFeature)).
Insert(string(ConsulConformanceFeature)).
Insert(string(NacosConformanceFeature))
var ExperimentFeatures = sets.Set{}.
Insert(string(WASMGoConformanceFeature)).
Insert(string(WASMCPPConformanceFeature))

View File

@@ -20,32 +20,41 @@ import (
"github.com/alibaba/higress/test/e2e/conformance/utils/config"
"github.com/alibaba/higress/test/e2e/conformance/utils/kubernetes"
"github.com/alibaba/higress/test/e2e/conformance/utils/roundtripper"
"istio.io/istio/pilot/pkg/util/sets"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// ConformanceTestSuite defines the test suite used to run Gateway API
// conformance tests.
type ConformanceTestSuite struct {
Client client.Client
RoundTripper roundtripper.RoundTripper
GatewayAddress string
IngressClassName string
Debug bool
Cleanup bool
BaseManifests string
Applier kubernetes.Applier
TimeoutConfig config.TimeoutConfig
Client client.Client
RoundTripper roundtripper.RoundTripper
GatewayAddress string
IngressClassName string
Debug bool
Cleanup bool
BaseManifests []string
Applier kubernetes.Applier
SkipTests sets.Set
TimeoutConfig config.TimeoutConfig
SupportedFeatures sets.Set
}
// Options can be used to initialize a ConformanceTestSuite.
type Options struct {
Client client.Client
GatewayAddress string
IngressClassName string
Debug bool
RoundTripper roundtripper.RoundTripper
BaseManifests string
NamespaceLabels map[string]string
SupportedFeatures sets.Set
ExemptFeatures sets.Set
EnableAllSupportedFeatures bool
Client client.Client
GatewayAddress string
IngressClassName string
Debug bool
RoundTripper roundtripper.RoundTripper
BaseManifests []string
NamespaceLabels map[string]string
// Options for wasm extended features
WASMOptions
// CleanupBaseResources indicates whether or not the base test
// resources such as Gateways should be cleaned up after the run.
@@ -53,6 +62,12 @@ type Options struct {
TimeoutConfig config.TimeoutConfig
}
type WASMOptions struct {
IsWasmPluginTest bool
WasmPluginType string
WasmPluginName string
}
// New returns a new ConformanceTestSuite.
func New(s Options) *ConformanceTestSuite {
config.SetupTimeoutConfig(&s.TimeoutConfig)
@@ -62,14 +77,33 @@ func New(s Options) *ConformanceTestSuite {
roundTripper = &roundtripper.DefaultRoundTripper{Debug: s.Debug, TimeoutConfig: s.TimeoutConfig}
}
if s.SupportedFeatures == nil {
s.SupportedFeatures = sets.Set{}
}
if s.IsWasmPluginTest {
if s.WasmPluginType == "CPP" {
s.SupportedFeatures.Insert(string(WASMCPPConformanceFeature))
} else {
s.SupportedFeatures.Insert(string(WASMGoConformanceFeature))
}
} else if s.EnableAllSupportedFeatures {
s.SupportedFeatures = AllFeatures
}
for feature := range s.ExemptFeatures {
s.SupportedFeatures.Delete(feature)
}
suite := &ConformanceTestSuite{
Client: s.Client,
RoundTripper: roundTripper,
IngressClassName: s.IngressClassName,
Debug: s.Debug,
Cleanup: s.CleanupBaseResources,
BaseManifests: s.BaseManifests,
GatewayAddress: s.GatewayAddress,
Client: s.Client,
RoundTripper: roundTripper,
IngressClassName: s.IngressClassName,
Debug: s.Debug,
Cleanup: s.CleanupBaseResources,
BaseManifests: s.BaseManifests,
SupportedFeatures: s.SupportedFeatures,
GatewayAddress: s.GatewayAddress,
Applier: kubernetes.Applier{
NamespaceLabels: s.NamespaceLabels,
},
@@ -77,8 +111,14 @@ func New(s Options) *ConformanceTestSuite {
}
// apply defaults
if suite.BaseManifests == "" {
suite.BaseManifests = "base/manifests.yaml"
if suite.BaseManifests == nil {
suite.BaseManifests = []string{
"base/manifests.yaml",
"base/consul.yaml",
"base/dubbo.yaml",
"base/eureka.yaml",
"base/nacos.yaml",
}
}
return suite
@@ -87,34 +127,36 @@ func New(s Options) *ConformanceTestSuite {
// Setup ensures the base resources required for conformance tests are installed
// in the cluster. It also ensures that all relevant resources are ready.
func (suite *ConformanceTestSuite) Setup(t *testing.T) {
t.Logf("Test Setup: Ensuring IngressClass has been accepted")
t.Logf("📦 Test Setup: Ensuring IngressClass has been accepted")
suite.Applier.IngressClass = suite.IngressClassName
t.Logf("Test Setup: Applying base manifests")
suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, suite.BaseManifests, suite.Cleanup)
t.Logf("📦 Test Setup: Applying base manifests")
t.Logf("Test Setup: Applying programmatic resources")
for _, baseManifest := range suite.BaseManifests {
suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, baseManifest, suite.Cleanup)
}
t.Logf("📦 Test Setup: Applying programmatic resources")
secret := kubernetes.MustCreateSelfSignedCertSecret(t, "higress-conformance-web-backend", "certificate", []string{"*"})
suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup)
secret = kubernetes.MustCreateSelfSignedCertSecret(t, "higress-conformance-infra", "tls-validity-checks-certificate", []string{"*"})
suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup)
t.Logf("Test Setup: Ensuring Pods from base manifests are ready")
t.Logf("📦 Test Setup: Ensuring Pods from base manifests are ready")
namespaces := []string{
"higress-conformance-infra",
"higress-conformance-app-backend",
"higress-conformance-web-backend",
"nacos-standlone-rc3",
"dubbo-demo-provider",
}
kubernetes.NamespacesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, namespaces)
t.Logf("🌱 Supported Features: %+v", suite.SupportedFeatures.UnsortedList())
}
// Run runs the provided set of conformance tests.
// RunWithTests runs the provided set of conformance tests.
func (suite *ConformanceTestSuite) Run(t *testing.T, tests []ConformanceTest) {
t.Logf("Start Running %d Test Cases: \n\n%s", len(tests), globalConformanceTestsListInfo(tests))
t.Logf("🚀 Start Running %d Test Cases: \n\n%s", len(tests), globalConformanceTestsListInfo(tests))
for _, test := range tests {
t.Run(test.ShortName, func(t *testing.T) {
test.Run(t, suite)
@@ -125,17 +167,20 @@ func (suite *ConformanceTestSuite) Run(t *testing.T, tests []ConformanceTest) {
func globalConformanceTestsListInfo(tests []ConformanceTest) string {
var cases string
for index, test := range tests {
cases += fmt.Sprintf("CaseNum: %d\nCaseName: %s\nScenario: %s\n\n", index+1, test.ShortName, test.Description)
cases += fmt.Sprintf("🎯 CaseNum: %d\nCaseName: %s\nScenario: %s\nFeatures: %+v\n\n", index+1, test.ShortName, test.Description, test.Features)
}
return cases
}
type ConformanceTests []ConformanceTest
// ConformanceTest is used to define each individual conformance test.
type ConformanceTest struct {
ShortName string
Description string
Manifests []string
Features []SupportedFeature
Slow bool
Parallel bool
Test func(*testing.T, *ConformanceTestSuite)
@@ -148,8 +193,23 @@ func (test *ConformanceTest) Run(t *testing.T, suite *ConformanceTestSuite) {
t.Parallel()
}
// Check that all features exercised by the test have been opted into by
// the suite.
for _, feature := range test.Features {
if !suite.SupportedFeatures.Contains(string(feature)) {
t.Skipf("🏊🏼 Skipping %s: suite does not support %s", test.ShortName, feature)
}
}
// check that the test should not be skipped
if suite.SkipTests.Contains(test.ShortName) {
t.Skipf("🏊🏼 Skipping %s: test explicitly skipped", test.ShortName)
}
t.Logf("🔥 Running Conformance Test: %s", test.ShortName)
for _, manifestLocation := range test.Manifests {
t.Logf("Applying %s", manifestLocation)
t.Logf("🧳 Applying Manifests: %s", manifestLocation)
suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, manifestLocation, true)
}

View File

@@ -16,7 +16,6 @@ package test
import (
"flag"
"strings"
"testing"
"github.com/stretchr/testify/require"
@@ -29,10 +28,6 @@ import (
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
)
var isWasmPluginTest = flag.Bool("isWasmPluginTest", false, "")
var wasmPluginType = flag.String("wasmPluginType", "GO", "")
var wasmPluginName = flag.String("wasmPluginName", "", "")
func TestHigressConformanceTests(t *testing.T) {
flag.Parse()
@@ -49,57 +44,15 @@ func TestHigressConformanceTests(t *testing.T) {
IngressClassName: *flags.IngressClassName,
Debug: *flags.ShowDebug,
CleanupBaseResources: *flags.CleanupBaseResources,
GatewayAddress: "localhost",
WASMOptions: suite.WASMOptions{
IsWasmPluginTest: *flags.IsWasmPluginTest,
WasmPluginName: *flags.WasmPluginName,
WasmPluginType: *flags.WasmPluginType,
},
GatewayAddress: "localhost",
EnableAllSupportedFeatures: true,
})
cSuite.Setup(t)
var higressTests []suite.ConformanceTest
if *isWasmPluginTest {
if strings.Compare(*wasmPluginType, "CPP") == 0 {
m := make(map[string]suite.ConformanceTest)
m["request_block"] = tests.CPPWasmPluginsRequestBlock
m["key_auth"] = tests.CPPWasmPluginsKeyAuth
higressTests = []suite.ConformanceTest{
m[*wasmPluginName],
}
} else {
higressTests = []suite.ConformanceTest{
tests.WasmPluginsRequestBlock,
}
}
} else {
higressTests = []suite.ConformanceTest{
tests.HTTPRouteSimpleSameNamespace,
tests.HTTPRouteHostNameSameNamespace,
tests.HTTPRouteRewritePath,
tests.HTTPRouteRewriteHost,
tests.HTTPRouteCanaryHeader,
tests.HTTPRouteEnableCors,
tests.HTTPRouteEnableIgnoreCase,
tests.HTTPRouteMatchMethods,
tests.HTTPRouteMatchQueryParams,
tests.HTTPRouteMatchHeaders,
tests.HTTPRouteAppRoot,
tests.HTTPRoutePermanentRedirect,
tests.HTTPRoutePermanentRedirectCode,
tests.HTTPRouteTemporalRedirect,
tests.HTTPRouteSameHostAndPath,
tests.HTTPRouteCanaryHeaderWithCustomizedHeader,
tests.HTTPRouteWhitelistSourceRange,
tests.HTTPRouteCanaryWeight,
tests.HTTPRouteMatchPath,
tests.HttpForceRedirectHttps,
tests.HttpRedirectAsHttps,
tests.HTTPRouteRequestHeaderControl,
tests.HTTPRouteDownstreamEncryption,
tests.HTTPRouteFullPathRegex,
tests.HTTPRouteHttp2Rpc,
tests.HTTPRouteConsulHttpBin,
tests.HTTPRouteEurekaRegistry,
}
}
cSuite.Run(t, higressTests)
cSuite.Run(t, tests.ConformanceTests)
}

View File

@@ -16,6 +16,8 @@
export VERSION
MODE="install"
HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)"
HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)"
HAS_DOCKER="$(type "docker" &> /dev/null && echo true || echo false)"
@@ -24,6 +26,7 @@ parseArgs() {
CONFIG_ARGS=()
DESTINATION=""
MODE="install"
if [[ $1 != "-"* ]]; then
DESTINATION="$1"
@@ -36,6 +39,10 @@ parseArgs() {
outputUsage
exit 0
;;
-u|--update)
MODE="update"
shift
;;
*)
CONFIG_ARGS+=("$1")
shift
@@ -48,15 +55,19 @@ parseArgs() {
validateArgs() {
if [ -d "$DESTINATION" ]; then
[ "$(ls -A "$DESTINATION")" ] && echo "The target folder \"$DESTINATION\" is not empty." && exit 1
if [ "$(ls -A "$DESTINATION")" -a "$MODE" != "update" ]; then
echo "The target folder \"$DESTINATION\" is not empty. Add \"-u\" to update an existed Higress instance." && exit 1
fi
if [ ! -w "$DESTINATION" ]; then
echo "The target folder \"$DESTINATION\" is not writeable."
exit 1
echo "The target folder \"$DESTINATION\" is not writeable." && exit 1
fi
else
if [ "$MODE" == "update" ]; then
echo "The target folder \"$DESTINATION\" for update doesn't exist." && exit 1
fi
mkdir -p "$DESTINATION"
if [ $? -ne 0 ]; then
exit -1
exit 1
fi
fi
@@ -67,11 +78,11 @@ validateArgs() {
outputUsage() {
echo "Usage: $(basename -- "$0") [DIR] [OPTIONS...]"
echo 'Install Higress (standalone version) into the DIR (the current directory by default).'
echo 'Install Higress (standalone version) into the DIR ("./higress" by default).'
echo '
-c, --config-url=URL URL of the config storage
Use Nacos with format: nacos://192.168.0.1:8848
Use local files with format: file://opt/higress/conf
Use local files with format: file:///opt/higress/conf
--use-builtin-nacos use the built-in Nacos service instead of
an external one
--nacos-ns=NACOS-NAMESPACE
@@ -104,6 +115,8 @@ outputUsage() {
--console-port=CONSOLE-PORT
the port used to visit Higress Console
default to 8080 if unspecified
-u, --update update an existed Higress instance.
no user configuration will be changed during update.
-h, --help give this help list'
}
@@ -143,7 +156,7 @@ runAsRoot() {
# verifySupported checks that the os/arch combination is supported for
# binary builds, as well whether or not necessary tools are present.
verifySupported() {
local supported="darwin-amd64\nlinux-amd64\nwindows-amd64\n"
local supported="darwin-amd64\nlinux-amd64\nwindows-amd64\ndarwin-arm64\nlinux-arm64\nwindows-arm64\n"
if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
echo "${OS}-${ARCH} platform isn't supported at the moment."
echo "Stay tuned for updates on https://github.com/alibaba/higress."
@@ -191,18 +204,46 @@ download() {
# install installs the product.
install() {
tar -zx --exclude="docs" --exclude="src" -f "$HIGRESS_TMP_FILE" -C "$DESTINATION" --strip-components=1
tar -zx --exclude="docs" --exclude="src" --exclude="test" -f "$HIGRESS_TMP_FILE" -C "$DESTINATION" --strip-components=1
echo -n "$VERSION" > "$DESTINATION/VERSION"
bash "$DESTINATION/bin/configure.sh" --auto-start ${CONFIG_ARGS[@]}
}
# update updates the product.
update() {
CURRENT_VERSION="0.0.0"
if [ -f "$DESTINATION/VERSION" ]; then
CURRENT_VERSION="$(cat "$DESTINATION/VERSION")"
fi
if [ "$CURRENT_VERSION" == "$VERSION" ]; then
echo "Higress is already up-to-date."
exit 0
fi
BACKUP_FOLDER="$(cd ${DESTINATION}/.. ; pwd)"
BACKUP_FILE="${BACKUP_FOLDER}/higress_backup_$(date '+%Y%m%d%H%M%S').tar.gz"
tar -zc -f "$BACKUP_FILE" -C "$DESTINATION" .
echo "The current version is packed here: $BACKUP_FILE"
echo ""
download
echo ""
tar -zx --exclude="docs" --exclude="src" --exclude="test" --exclude="compose/.env" -f "$HIGRESS_TMP_FILE" -C "$DESTINATION" --strip-components=1
tar -zx -f "$HIGRESS_TMP_FILE" -C "$DESTINATION" --transform='s/env/env_new/g' --strip-components=1 "higress-standalone-${VERSION#v}/compose/.env"
bash "$DESTINATION/bin/update.sh"
echo -n "$VERSION" > "$DESTINATION/VERSION"
return
}
# fail_trap is executed if an error occurs.
fail_trap() {
result=$?
if [ "$result" != "0" ]; then
if [ -n "$INPUT_ARGUMENTS" ]; then
echo "Failed to install Higress with the arguments provided: $INPUT_ARGUMENTS"
echo "Failed to ${MODE} Higress with the arguments provided: $INPUT_ARGUMENTS"
else
echo "Failed to install Higress"
echo "Failed to ${MODE} Higress"
fi
echo -e "\tFor support, go to https://github.com/alibaba/higress."
fi
@@ -228,6 +269,13 @@ initOS
verifySupported
checkDesiredVersion
download
install
case "$MODE" in
update)
update
;;
*)
download
install
;;
esac
cleanup

View File

@@ -20,37 +20,35 @@ set -euo pipefail
TYPE=${PLUGIN_TYPE-""}
INNER_PLUGIN_NAME=${PLUGIN_NAME-""}
if [ $TYPE == "CPP" ]
if [ "$TYPE" == "CPP" ]
then
cd ./plugins/wasm-cpp/
if [ ! -n "$INNER_PLUGIN_NAME" ]; then
echo "you must specify which cpp plugin you want to compile"
echo "You must specify which cpp plugin you want to compile"
else
echo "build wasmplugin-cpp name of $INNER_PLUGIN_NAME"
echo "🚀 Build CPP WasmPlugin: $INNER_PLUGIN_NAME"
PLUGIN_NAME=${INNER_PLUGIN_NAME} make build
fi
else
echo "not specify plugin language, so just compile wasm-go as default"
echo "Not specify plugin language, so just compile wasm-go as default"
cd ./plugins/wasm-go/
if [ ! -n "$INNER_PLUGIN_NAME" ]; then
EXTENSIONS_DIR=$(pwd)"/extensions/"
echo "build all wasmplugins-go under folder of $EXTENSIONS_DIR"
echo "🚀 Build all Go WasmPlugins under folder of $EXTENSIONS_DIR"
for file in `ls $EXTENSIONS_DIR`
do
# TODO: adjust waf build
if [ $file == "waf" ]; then
if [ "$file" == "waf" ]; then
continue
fi
if [ -d $EXTENSIONS_DIR$file ]; then
name=${file##*/}
echo "build wasmplugin name of $name"
echo "🚀 Build Go WasmPlugin: $name"
PLUGIN_NAME=${name} make build
fi
done
else
echo "build wasmplugin-go name of $INNER_PLUGIN_NAME"
echo "🚀 Build Go WasmPlugin: $INNER_PLUGIN_NAME"
PLUGIN_NAME=${INNER_PLUGIN_NAME} make build
fi
fi