mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 21:21:01 +08:00
Compare commits
60 Commits
v2.0.0-rc.
...
v2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a53c7b4d3 | ||
|
|
ae6dab919d | ||
|
|
601b205abc | ||
|
|
9972e7611a | ||
|
|
c30ca5dd9e | ||
|
|
e26a2a37d7 | ||
|
|
f20c48e960 | ||
|
|
e126f3a888 | ||
|
|
93317adbc7 | ||
|
|
ecf52aecfc | ||
|
|
3ed28f2a66 | ||
|
|
4d0d8a7f50 | ||
|
|
1f8d50c0b1 | ||
|
|
14b11dcb05 | ||
|
|
71aae9ddf6 | ||
|
|
1b119ed371 | ||
|
|
ea99159d51 | ||
|
|
567d7c25f3 | ||
|
|
708e7af79a | ||
|
|
260772926c | ||
|
|
af4e34b7ed | ||
|
|
8293042c25 | ||
|
|
1acaaea222 | ||
|
|
e004321cb0 | ||
|
|
b82853c653 | ||
|
|
bef9139753 | ||
|
|
dc61bfc5c5 | ||
|
|
b24731593f | ||
|
|
e7761a2ecc | ||
|
|
86239c4a4b | ||
|
|
c923e5cb42 | ||
|
|
ee67553816 | ||
|
|
ffc5458a91 | ||
|
|
55f6ed7953 | ||
|
|
9e5188cfca | ||
|
|
f51408d7ff | ||
|
|
0471249e7f | ||
|
|
59fe661cd2 | ||
|
|
7610c9f504 | ||
|
|
452bd4ef2d | ||
|
|
1db37f988a | ||
|
|
c7c4ae1da2 | ||
|
|
0f9113ed82 | ||
|
|
3eda1f3c70 | ||
|
|
6439b2d4da | ||
|
|
a4e696b957 | ||
|
|
f471c0a99f | ||
|
|
ffe3aceefc | ||
|
|
d18e22432a | ||
|
|
d682e05c75 | ||
|
|
a382bb201c | ||
|
|
6777d29c6e | ||
|
|
93fd1a5772 | ||
|
|
d6a67c05e6 | ||
|
|
28d5145514 | ||
|
|
1ab8025c96 | ||
|
|
ea862cfd4c | ||
|
|
f03ce572be | ||
|
|
aefa3b94c4 | ||
|
|
b7a30669a1 |
@@ -89,7 +89,9 @@ jobs:
|
||||
push_command=${push_command%\"} # 删除PUSH_COMMAND中的双引号,确保oras push正常解析
|
||||
|
||||
target_image="${{ env.IMAGE_REGISTRY_SERVICE }}/${{ env.IMAGE_REPOSITORY}}/${{ env.PLUGIN_NAME }}:${{ env.VERSION }}"
|
||||
target_image_latest="${{ env.IMAGE_REGISTRY_SERVICE }}/${{ env.IMAGE_REPOSITORY}}/${{ env.PLUGIN_NAME }}:latest"
|
||||
echo "TargetImage=${target_image}"
|
||||
echo "TargetImageLatest=${target_image_latest}"
|
||||
|
||||
cd ${{ github.workspace }}/plugins/wasm-go/extensions/${PLUGIN_NAME}
|
||||
if [ -f ./.buildrc ]; then
|
||||
@@ -108,7 +110,6 @@ jobs:
|
||||
tar czvf plugin.tar.gz plugin.wasm
|
||||
echo ${{ secrets.REGISTRY_PASSWORD }} | oras login -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin ${{ env.IMAGE_REGISTRY_SERVICE }}
|
||||
oras push ${target_image} ${push_command}
|
||||
oras push ${target_image_latest} ${push_command}
|
||||
"
|
||||
docker exec builder bash -c "$command"
|
||||
|
||||
|
||||
|
||||
8
.github/workflows/build-and-test-plugin.yaml
vendored
8
.github/workflows/build-and-test-plugin.yaml
vendored
@@ -64,14 +64,6 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |-
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-cache-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules-cache
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
- name: "Run Ingress WasmPlugins Tests"
|
||||
|
||||
24
.github/workflows/build-and-test.yaml
vendored
24
.github/workflows/build-and-test.yaml
vendored
@@ -37,14 +37,6 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |-
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-cache-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules-cache
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
# test
|
||||
@@ -83,14 +75,6 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |-
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-cache-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules-cache
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
- name: "Build Higress Binary"
|
||||
@@ -137,14 +121,6 @@ jobs:
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |-
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-cache-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules-cache
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
|
||||
30
.github/workflows/build-image-and-push.yaml
vendored
30
.github/workflows/build-image-and-push.yaml
vendored
@@ -44,16 +44,6 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-new-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules-new
|
||||
|
||||
- name: Calculate Docker metadata
|
||||
id: docker-meta
|
||||
uses: docker/metadata-action@v5
|
||||
@@ -120,16 +110,6 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules-new
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
@@ -208,16 +188,6 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules-new
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/istio @SpecialYang @johnlanni
|
||||
/pkg @SpecialYang @johnlanni @CH3CHO
|
||||
/plugins @johnlanni @WeixinX @CH3CHO
|
||||
/plugins/wasm-rust @007gzs
|
||||
/registry @NameHaibinZhang @2456868764 @johnlanni
|
||||
/test @Xunzhuo @2456868764 @CH3CHO
|
||||
/tools @johnlanni @Xunzhuo @2456868764
|
||||
|
||||
4
Makefile
4
Makefile
@@ -32,7 +32,7 @@ export BUILD_WITH_CONTAINER ?= 0
|
||||
|
||||
ifeq ($(BUILD_WITH_CONTAINER),1)
|
||||
|
||||
# An export free of arugments in a Makefile places all variables in the Makefile into the
|
||||
# An export free of arguments in a Makefile places all variables in the Makefile into the
|
||||
# environment. This is needed to allow overrides from Makefile.overrides.mk.
|
||||
export
|
||||
|
||||
@@ -60,7 +60,7 @@ else
|
||||
$(shell mkdir -p out)
|
||||
$(shell $(shell pwd)/tools/hack/setup_env.sh envfile > out/.env)
|
||||
include out/.env
|
||||
# An export free of arugments in a Makefile places all variables in the Makefile into the
|
||||
# An export free of arguments in a Makefile places all variables in the Makefile into the
|
||||
# environment. This behavior may be surprising to many that use shell often, which simply
|
||||
# displays the existing environment
|
||||
export
|
||||
|
||||
@@ -25,7 +25,7 @@ TARGET_ARCH ?= amd64
|
||||
|
||||
GOARCH_LOCAL := $(TARGET_ARCH)
|
||||
GOOS_LOCAL := $(TARGET_OS)
|
||||
RELEASE_LDFLAGS='$(GO_LDFLAGS) -extldflags -static -s -w -X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'
|
||||
RELEASE_LDFLAGS='$(GO_LDFLAGS) -extldflags -static -s -w'
|
||||
|
||||
export OUT:=$(TARGET_OUT)
|
||||
export OUT_LINUX:=$(TARGET_OUT_LINUX)
|
||||
@@ -68,7 +68,7 @@ default: build
|
||||
|
||||
.PHONY: go.test.coverage
|
||||
go.test.coverage: prebuild
|
||||
go test -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" ./cmd/... ./pkg/... -race -coverprofile=coverage.xml -covermode=atomic
|
||||
go test ./cmd/... ./pkg/... -race -coverprofile=coverage.xml -covermode=atomic
|
||||
|
||||
.PHONY: build
|
||||
build: prebuild $(OUT)
|
||||
@@ -101,11 +101,11 @@ build-hgctl-multiarch: prebuild $(OUT)
|
||||
|
||||
.PHONY: build-hgctl-macos-arm64
|
||||
build-hgctl-macos-arm64: prebuild $(OUT)
|
||||
CGO_ENABLED=1 GOPROXY=$(GOPROXY) GOOS=darwin GOARCH=arm64 LDFLAGS="-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" PROJECT_DIR="$(HGCTL_PROJECT_DIR)" tools/hack/gobuild.sh ../out/darwin_arm64/ $(HGCTL_BINARIES)
|
||||
CGO_ENABLED=1 STATIC=0 GOPROXY=$(GOPROXY) GOOS=darwin GOARCH=arm64 PROJECT_DIR="$(HGCTL_PROJECT_DIR)" tools/hack/gobuild.sh ../out/darwin_arm64/ $(HGCTL_BINARIES)
|
||||
|
||||
.PHONY: build-hgctl-macos-amd64
|
||||
build-hgctl-macos-amd64: prebuild $(OUT)
|
||||
CGO_ENABLED=1 GOPROXY=$(GOPROXY) GOOS=darwin GOARCH=amd64 LDFLAGS="-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" PROJECT_DIR="$(HGCTL_PROJECT_DIR)" tools/hack/gobuild.sh ../out/darwin_amd64/ $(HGCTL_BINARIES)
|
||||
CGO_ENABLED=1 STATIC=0 GOPROXY=$(GOPROXY) GOOS=darwin GOARCH=amd64 PROJECT_DIR="$(HGCTL_PROJECT_DIR)" tools/hack/gobuild.sh ../out/darwin_amd64/ $(HGCTL_BINARIES)
|
||||
|
||||
# Create targets for OUT_LINUX/binary
|
||||
# There are two use cases here:
|
||||
@@ -144,7 +144,7 @@ docker-buildx-push: clean-env docker.higress-buildx
|
||||
export PARENT_GIT_TAG:=$(shell cat VERSION)
|
||||
export PARENT_GIT_REVISION:=$(TAG)
|
||||
|
||||
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.0.0-rc.1/envoy-symbol-ARCH.tar.gz
|
||||
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.0.0/envoy-symbol-ARCH.tar.gz
|
||||
|
||||
build-envoy: prebuild
|
||||
./tools/hack/build-envoy.sh
|
||||
@@ -187,8 +187,8 @@ install: pre-install
|
||||
cd helm/higress; helm dependency build
|
||||
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
|
||||
|
||||
ENVOY_LATEST_IMAGE_TAG ?= a6c313d41b3b54f0e3ed81fc676c520160cfed05
|
||||
ISTIO_LATEST_IMAGE_TAG ?= a9a55b3895bbf64a1ad8f724b2de3de017831e38
|
||||
ENVOY_LATEST_IMAGE_TAG ?= 2.0.1
|
||||
ISTIO_LATEST_IMAGE_TAG ?= 2.0.1
|
||||
|
||||
install-dev: pre-install
|
||||
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'
|
||||
@@ -285,11 +285,11 @@ delete-cluster: $(tools/kind) ## Delete kind cluster.
|
||||
kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster using the provided $IMAGE and $TAG.
|
||||
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress $(TAG)
|
||||
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/dubbo-provider-demo 0.0.3-x86
|
||||
tools/hack/docker-pull-image.sh docker.io/alihigress/nacos-standlone-rc3 1.0.0-RC3
|
||||
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/nacos-standlone-rc3 1.0.0-RC3
|
||||
tools/hack/docker-pull-image.sh docker.io/hashicorp/consul 1.16.0
|
||||
tools/hack/docker-pull-image.sh docker.io/charlie1380/eureka-registry-provider v0.3.0
|
||||
tools/hack/docker-pull-image.sh docker.io/bitinit/eureka latest
|
||||
tools/hack/docker-pull-image.sh docker.io/alihigress/httpbin 1.0.2
|
||||
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/httpbin 1.0.2
|
||||
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server 1.3.0
|
||||
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server v1.0
|
||||
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-body 1.0.0
|
||||
@@ -297,9 +297,9 @@ kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster us
|
||||
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin 1.0.2
|
||||
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3
|
||||
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/dubbo-provider-demo 0.0.3-x86
|
||||
tools/hack/kind-load-image.sh docker.io/alihigress/nacos-standlone-rc3 1.0.0-RC3
|
||||
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/nacos-standlone-rc3 1.0.0-RC3
|
||||
tools/hack/kind-load-image.sh docker.io/hashicorp/consul 1.16.0
|
||||
tools/hack/kind-load-image.sh docker.io/alihigress/httpbin 1.0.2
|
||||
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/httpbin 1.0.2
|
||||
tools/hack/kind-load-image.sh docker.io/charlie1380/eureka-registry-provider v0.3.0
|
||||
tools/hack/kind-load-image.sh docker.io/bitinit/eureka latest
|
||||
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server 1.3.0
|
||||
|
||||
17
README.md
17
README.md
@@ -8,11 +8,12 @@
|
||||
[](https://github.com/alibaba/higress/actions)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
|
||||
[**官网**](https://higress.io/) |
|
||||
[**文档**](https://higress.io/docs/latest/user/quickstart/) |
|
||||
[**博客**](https://higress.io/blog/) |
|
||||
[**开发指引**](https://higress.io/docs/latest/dev/architecture/) |
|
||||
[**AI插件**](https://higress.io/plugin/)
|
||||
[**官网**](https://higress.cn/) |
|
||||
[**文档**](https://higress.cn/docs/latest/user/quickstart/) |
|
||||
[**博客**](https://higress.cn/blog/) |
|
||||
[**电子书**](https://higress.cn/docs/ebook/wasm14/) |
|
||||
[**开发指引**](https://higress.cn/docs/latest/dev/architecture/) |
|
||||
[**AI插件**](https://higress.cn/plugin/)
|
||||
|
||||
|
||||
<p>
|
||||
@@ -59,7 +60,7 @@ docker run -d --rm --name higress-ai -v ${PWD}:/data \
|
||||
|
||||
**Higress 的所有 Docker 镜像都一直使用自己独享的仓库,不受 Docker Hub 境内不可访问的影响**
|
||||
|
||||
K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start 文档](https://higress.io/docs/latest/user/quickstart/)。
|
||||
K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start 文档](https://higress.cn/docs/latest/user/quickstart/)。
|
||||
|
||||
|
||||
## 使用场景
|
||||
@@ -172,3 +173,7 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start
|
||||
|
||||

|
||||
|
||||
### 关联仓库
|
||||
|
||||
- Higress 控制台:https://github.com/higress-group/higress-console
|
||||
- Higress(独立运行版):https://github.com/higress-group/higress-standalone
|
||||
@@ -82,3 +82,8 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
|
||||
### Thanks
|
||||
|
||||
Higress would not be possible without the valuable open-source work of projects in the community. We would like to extend a special thank-you to Envoy and Istio.
|
||||
|
||||
### Related Repositories
|
||||
|
||||
- Higress Console: https://github.com/higress-group/higress-console
|
||||
- Higress Standalone: https://github.com/higress-group/higress-standalone
|
||||
@@ -17,7 +17,7 @@
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc (unknown)
|
||||
// source: extensions/v1alpha1/wasm.proto
|
||||
// source: extensions/v1alpha1/wasmplugin.proto
|
||||
|
||||
// $schema: higress.extensions.v1alpha1.WasmPlugin
|
||||
// $title: WasmPlugin
|
||||
@@ -84,11 +84,11 @@ func (x PluginPhase) String() string {
|
||||
}
|
||||
|
||||
func (PluginPhase) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_extensions_v1alpha1_wasm_proto_enumTypes[0].Descriptor()
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (PluginPhase) Type() protoreflect.EnumType {
|
||||
return &file_extensions_v1alpha1_wasm_proto_enumTypes[0]
|
||||
return &file_extensions_v1alpha1_wasmplugin_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x PluginPhase) Number() protoreflect.EnumNumber {
|
||||
@@ -97,7 +97,7 @@ func (x PluginPhase) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use PluginPhase.Descriptor instead.
|
||||
func (PluginPhase) EnumDescriptor() ([]byte, []int) {
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescGZIP(), []int{0}
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// The pull behaviour to be applied when fetching an OCI image,
|
||||
@@ -146,11 +146,11 @@ func (x PullPolicy) String() string {
|
||||
}
|
||||
|
||||
func (PullPolicy) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_extensions_v1alpha1_wasm_proto_enumTypes[1].Descriptor()
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (PullPolicy) Type() protoreflect.EnumType {
|
||||
return &file_extensions_v1alpha1_wasm_proto_enumTypes[1]
|
||||
return &file_extensions_v1alpha1_wasmplugin_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x PullPolicy) Number() protoreflect.EnumNumber {
|
||||
@@ -159,7 +159,7 @@ func (x PullPolicy) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use PullPolicy.Descriptor instead.
|
||||
func (PullPolicy) EnumDescriptor() ([]byte, []int) {
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescGZIP(), []int{1}
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
type EnvValueSource int32
|
||||
@@ -194,11 +194,11 @@ func (x EnvValueSource) String() string {
|
||||
}
|
||||
|
||||
func (EnvValueSource) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_extensions_v1alpha1_wasm_proto_enumTypes[2].Descriptor()
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_enumTypes[2].Descriptor()
|
||||
}
|
||||
|
||||
func (EnvValueSource) Type() protoreflect.EnumType {
|
||||
return &file_extensions_v1alpha1_wasm_proto_enumTypes[2]
|
||||
return &file_extensions_v1alpha1_wasmplugin_proto_enumTypes[2]
|
||||
}
|
||||
|
||||
func (x EnvValueSource) Number() protoreflect.EnumNumber {
|
||||
@@ -207,7 +207,7 @@ func (x EnvValueSource) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use EnvValueSource.Descriptor instead.
|
||||
func (EnvValueSource) EnumDescriptor() ([]byte, []int) {
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescGZIP(), []int{2}
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
type FailStrategy int32
|
||||
@@ -246,11 +246,11 @@ func (x FailStrategy) String() string {
|
||||
}
|
||||
|
||||
func (FailStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_extensions_v1alpha1_wasm_proto_enumTypes[3].Descriptor()
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_enumTypes[3].Descriptor()
|
||||
}
|
||||
|
||||
func (FailStrategy) Type() protoreflect.EnumType {
|
||||
return &file_extensions_v1alpha1_wasm_proto_enumTypes[3]
|
||||
return &file_extensions_v1alpha1_wasmplugin_proto_enumTypes[3]
|
||||
}
|
||||
|
||||
func (x FailStrategy) Number() protoreflect.EnumNumber {
|
||||
@@ -259,7 +259,7 @@ func (x FailStrategy) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use FailStrategy.Descriptor instead.
|
||||
func (FailStrategy) EnumDescriptor() ([]byte, []int) {
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescGZIP(), []int{3}
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
// <!-- crd generation tags
|
||||
@@ -347,7 +347,7 @@ type WasmPlugin struct {
|
||||
func (x *WasmPlugin) Reset() {
|
||||
*x = WasmPlugin{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_extensions_v1alpha1_wasm_proto_msgTypes[0]
|
||||
mi := &file_extensions_v1alpha1_wasmplugin_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -360,7 +360,7 @@ func (x *WasmPlugin) String() string {
|
||||
func (*WasmPlugin) ProtoMessage() {}
|
||||
|
||||
func (x *WasmPlugin) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_extensions_v1alpha1_wasm_proto_msgTypes[0]
|
||||
mi := &file_extensions_v1alpha1_wasmplugin_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -373,7 +373,7 @@ func (x *WasmPlugin) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use WasmPlugin.ProtoReflect.Descriptor instead.
|
||||
func (*WasmPlugin) Descriptor() ([]byte, []int) {
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescGZIP(), []int{0}
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *WasmPlugin) GetUrl() string {
|
||||
@@ -490,7 +490,7 @@ type MatchRule struct {
|
||||
func (x *MatchRule) Reset() {
|
||||
*x = MatchRule{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_extensions_v1alpha1_wasm_proto_msgTypes[1]
|
||||
mi := &file_extensions_v1alpha1_wasmplugin_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -503,7 +503,7 @@ func (x *MatchRule) String() string {
|
||||
func (*MatchRule) ProtoMessage() {}
|
||||
|
||||
func (x *MatchRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_extensions_v1alpha1_wasm_proto_msgTypes[1]
|
||||
mi := &file_extensions_v1alpha1_wasmplugin_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -516,7 +516,7 @@ func (x *MatchRule) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use MatchRule.ProtoReflect.Descriptor instead.
|
||||
func (*MatchRule) Descriptor() ([]byte, []int) {
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescGZIP(), []int{1}
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *MatchRule) GetIngress() []string {
|
||||
@@ -569,7 +569,7 @@ type VmConfig struct {
|
||||
func (x *VmConfig) Reset() {
|
||||
*x = VmConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_extensions_v1alpha1_wasm_proto_msgTypes[2]
|
||||
mi := &file_extensions_v1alpha1_wasmplugin_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -582,7 +582,7 @@ func (x *VmConfig) String() string {
|
||||
func (*VmConfig) ProtoMessage() {}
|
||||
|
||||
func (x *VmConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_extensions_v1alpha1_wasm_proto_msgTypes[2]
|
||||
mi := &file_extensions_v1alpha1_wasmplugin_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -595,7 +595,7 @@ func (x *VmConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use VmConfig.ProtoReflect.Descriptor instead.
|
||||
func (*VmConfig) Descriptor() ([]byte, []int) {
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescGZIP(), []int{2}
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *VmConfig) GetEnv() []*EnvVar {
|
||||
@@ -625,7 +625,7 @@ type EnvVar struct {
|
||||
func (x *EnvVar) Reset() {
|
||||
*x = EnvVar{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_extensions_v1alpha1_wasm_proto_msgTypes[3]
|
||||
mi := &file_extensions_v1alpha1_wasmplugin_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -638,7 +638,7 @@ func (x *EnvVar) String() string {
|
||||
func (*EnvVar) ProtoMessage() {}
|
||||
|
||||
func (x *EnvVar) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_extensions_v1alpha1_wasm_proto_msgTypes[3]
|
||||
mi := &file_extensions_v1alpha1_wasmplugin_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -651,7 +651,7 @@ func (x *EnvVar) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use EnvVar.ProtoReflect.Descriptor instead.
|
||||
func (*EnvVar) Descriptor() ([]byte, []int) {
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescGZIP(), []int{3}
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *EnvVar) GetName() string {
|
||||
@@ -675,124 +675,125 @@ func (x *EnvVar) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_extensions_v1alpha1_wasm_proto protoreflect.FileDescriptor
|
||||
var File_extensions_v1alpha1_wasmplugin_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_extensions_v1alpha1_wasm_proto_rawDesc = []byte{
|
||||
0x0a, 0x1e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x61,
|
||||
0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x77, 0x61, 0x73, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x12, 0x1b, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77,
|
||||
0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73,
|
||||
0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8d, 0x06, 0x0a, 0x0a,
|
||||
0x57, 0x61, 0x73, 0x6d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72,
|
||||
0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x68,
|
||||
0x61, 0x32, 0x35, 0x36, 0x12, 0x53, 0x0a, 0x11, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x75,
|
||||
0x6c, 0x6c, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x27, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x75,
|
||||
0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x50,
|
||||
0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6d, 0x61,
|
||||
0x67, 0x65, 0x5f, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x50, 0x75, 0x6c, 0x6c, 0x53,
|
||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79,
|
||||
0x12, 0x3c, 0x0a, 0x0d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74,
|
||||
0x52, 0x0c, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x3e, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28,
|
||||
0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x6c, 0x75,
|
||||
0x67, 0x69, 0x6e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12,
|
||||
0x37, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08,
|
||||
0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x4e, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c,
|
||||
0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x29, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x61,
|
||||
0x69, 0x6c, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0c, 0x66, 0x61, 0x69, 0x6c,
|
||||
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x42, 0x0a, 0x09, 0x76, 0x6d, 0x5f, 0x63,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x69,
|
||||
0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x6d, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x52, 0x08, 0x76, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0e,
|
||||
0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x65,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0d, 0x64,
|
||||
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x47, 0x0a, 0x0b,
|
||||
0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x66, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x26, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65,
|
||||
var file_extensions_v1alpha1_wasmplugin_proto_rawDesc = []byte{
|
||||
0x0a, 0x24, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x61,
|
||||
0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x77, 0x61, 0x73, 0x6d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e,
|
||||
0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
|
||||
0x68, 0x61, 0x31, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x22, 0x8d, 0x06, 0x0a, 0x0a, 0x57, 0x61, 0x73, 0x6d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75,
|
||||
0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x53, 0x0a, 0x11, 0x69, 0x6d,
|
||||
0x61, 0x67, 0x65, 0x5f, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e,
|
||||
0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
|
||||
0x68, 0x61, 0x31, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0f,
|
||||
0x69, 0x6d, 0x61, 0x67, 0x65, 0x50, 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12,
|
||||
0x2a, 0x0a, 0x11, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x69, 0x6d, 0x61, 0x67,
|
||||
0x65, 0x50, 0x75, 0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x76,
|
||||
0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x18,
|
||||
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x3c, 0x0a, 0x0d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0c, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x6c, 0x75, 0x67, 0x69,
|
||||
0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x09,
|
||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65,
|
||||
0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
|
||||
0x61, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x52, 0x05,
|
||||
0x70, 0x68, 0x61, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74,
|
||||
0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x4e,
|
||||
0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
|
||||
0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e,
|
||||
0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
|
||||
0x68, 0x61, 0x31, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||
0x52, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x42,
|
||||
0x0a, 0x09, 0x76, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x25, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65,
|
||||
0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e,
|
||||
0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68,
|
||||
0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
|
||||
0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18,
|
||||
0x67, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x22, 0xaf, 0x01, 0x0a, 0x09,
|
||||
0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x67,
|
||||
0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e, 0x67, 0x72,
|
||||
0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20,
|
||||
0x03, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2f, 0x0a, 0x06, 0x63,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
|
||||
0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e,
|
||||
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x05,
|
||||
0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x41, 0x0a,
|
||||
0x08, 0x56, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x03, 0x65, 0x6e, 0x76,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73,
|
||||
0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c,
|
||||
0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x52, 0x03, 0x65, 0x6e, 0x76,
|
||||
0x22, 0x7e, 0x0a, 0x06, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4a,
|
||||
0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74,
|
||||
0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
|
||||
0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,
|
||||
0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x2a, 0x45, 0x0a, 0x0b, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12,
|
||||
0x15, 0x0a, 0x11, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x50,
|
||||
0x48, 0x41, 0x53, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x4e, 0x10,
|
||||
0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x5a, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05,
|
||||
0x53, 0x54, 0x41, 0x54, 0x53, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x0a, 0x50, 0x75, 0x6c, 0x6c, 0x50,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
|
||||
0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x10, 0x00, 0x12, 0x10, 0x0a,
|
||||
0x0c, 0x49, 0x66, 0x4e, 0x6f, 0x74, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x10, 0x01, 0x12,
|
||||
0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x02, 0x2a, 0x26, 0x0a, 0x0e, 0x45,
|
||||
0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x49, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53,
|
||||
0x54, 0x10, 0x01, 0x2a, 0x2d, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x72, 0x61, 0x74,
|
||||
0x65, 0x67, 0x79, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x41, 0x49, 0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53,
|
||||
0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x41, 0x49, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e,
|
||||
0x10, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73,
|
||||
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f,
|
||||
0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x56, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x76, 0x6d, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
||||
0x75, 0x63, 0x74, 0x52, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x47, 0x0a, 0x0b, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x72, 0x75, 0x6c, 0x65,
|
||||
0x73, 0x18, 0x66, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73,
|
||||
0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61,
|
||||
0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x52,
|
||||
0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x64,
|
||||
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69,
|
||||
0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x67, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x65, 0x66,
|
||||
0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c,
|
||||
0x65, 0x22, 0xaf, 0x01, 0x0a, 0x09, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09,
|
||||
0x52, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x73,
|
||||
0x61, 0x62, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72,
|
||||
0x76, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x22, 0x41, 0x0a, 0x08, 0x56, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||
0x35, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68,
|
||||
0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61,
|
||||
0x72, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x22, 0x7e, 0x0a, 0x06, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x72,
|
||||
0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65,
|
||||
0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31,
|
||||
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x72, 0x6f, 0x6d,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x45, 0x0a, 0x0b, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
|
||||
0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x48, 0x41, 0x53, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
|
||||
0x41, 0x55, 0x54, 0x48, 0x4e, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x5a,
|
||||
0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x54, 0x53, 0x10, 0x03, 0x2a, 0x42, 0x0a,
|
||||
0x0a, 0x50, 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x55,
|
||||
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43,
|
||||
0x59, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x66, 0x4e, 0x6f, 0x74, 0x50, 0x72, 0x65, 0x73,
|
||||
0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10,
|
||||
0x02, 0x2a, 0x26, 0x0a, 0x0e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75,
|
||||
0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12,
|
||||
0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x01, 0x2a, 0x2d, 0x0a, 0x0c, 0x46, 0x61, 0x69,
|
||||
0x6c, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68,
|
||||
0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_extensions_v1alpha1_wasm_proto_rawDescOnce sync.Once
|
||||
file_extensions_v1alpha1_wasm_proto_rawDescData = file_extensions_v1alpha1_wasm_proto_rawDesc
|
||||
file_extensions_v1alpha1_wasmplugin_proto_rawDescOnce sync.Once
|
||||
file_extensions_v1alpha1_wasmplugin_proto_rawDescData = file_extensions_v1alpha1_wasmplugin_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_extensions_v1alpha1_wasm_proto_rawDescGZIP() []byte {
|
||||
file_extensions_v1alpha1_wasm_proto_rawDescOnce.Do(func() {
|
||||
file_extensions_v1alpha1_wasm_proto_rawDescData = protoimpl.X.CompressGZIP(file_extensions_v1alpha1_wasm_proto_rawDescData)
|
||||
func file_extensions_v1alpha1_wasmplugin_proto_rawDescGZIP() []byte {
|
||||
file_extensions_v1alpha1_wasmplugin_proto_rawDescOnce.Do(func() {
|
||||
file_extensions_v1alpha1_wasmplugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_extensions_v1alpha1_wasmplugin_proto_rawDescData)
|
||||
})
|
||||
return file_extensions_v1alpha1_wasm_proto_rawDescData
|
||||
return file_extensions_v1alpha1_wasmplugin_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_extensions_v1alpha1_wasm_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
var file_extensions_v1alpha1_wasm_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_extensions_v1alpha1_wasm_proto_goTypes = []interface{}{
|
||||
var file_extensions_v1alpha1_wasmplugin_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
var file_extensions_v1alpha1_wasmplugin_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_extensions_v1alpha1_wasmplugin_proto_goTypes = []interface{}{
|
||||
(PluginPhase)(0), // 0: higress.extensions.v1alpha1.PluginPhase
|
||||
(PullPolicy)(0), // 1: higress.extensions.v1alpha1.PullPolicy
|
||||
(EnvValueSource)(0), // 2: higress.extensions.v1alpha1.EnvValueSource
|
||||
@@ -804,7 +805,7 @@ var file_extensions_v1alpha1_wasm_proto_goTypes = []interface{}{
|
||||
(*_struct.Struct)(nil), // 8: google.protobuf.Struct
|
||||
(*wrappers.Int32Value)(nil), // 9: google.protobuf.Int32Value
|
||||
}
|
||||
var file_extensions_v1alpha1_wasm_proto_depIdxs = []int32{
|
||||
var file_extensions_v1alpha1_wasmplugin_proto_depIdxs = []int32{
|
||||
1, // 0: higress.extensions.v1alpha1.WasmPlugin.image_pull_policy:type_name -> higress.extensions.v1alpha1.PullPolicy
|
||||
8, // 1: higress.extensions.v1alpha1.WasmPlugin.plugin_config:type_name -> google.protobuf.Struct
|
||||
0, // 2: higress.extensions.v1alpha1.WasmPlugin.phase:type_name -> higress.extensions.v1alpha1.PluginPhase
|
||||
@@ -823,13 +824,13 @@ var file_extensions_v1alpha1_wasm_proto_depIdxs = []int32{
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_extensions_v1alpha1_wasm_proto_init() }
|
||||
func file_extensions_v1alpha1_wasm_proto_init() {
|
||||
if File_extensions_v1alpha1_wasm_proto != nil {
|
||||
func init() { file_extensions_v1alpha1_wasmplugin_proto_init() }
|
||||
func file_extensions_v1alpha1_wasmplugin_proto_init() {
|
||||
if File_extensions_v1alpha1_wasmplugin_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_extensions_v1alpha1_wasm_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_extensions_v1alpha1_wasmplugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WasmPlugin); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -841,7 +842,7 @@ func file_extensions_v1alpha1_wasm_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_extensions_v1alpha1_wasm_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_extensions_v1alpha1_wasmplugin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MatchRule); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -853,7 +854,7 @@ func file_extensions_v1alpha1_wasm_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_extensions_v1alpha1_wasm_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_extensions_v1alpha1_wasmplugin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*VmConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -865,7 +866,7 @@ func file_extensions_v1alpha1_wasm_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_extensions_v1alpha1_wasm_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_extensions_v1alpha1_wasmplugin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*EnvVar); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -882,19 +883,19 @@ func file_extensions_v1alpha1_wasm_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_extensions_v1alpha1_wasm_proto_rawDesc,
|
||||
RawDescriptor: file_extensions_v1alpha1_wasmplugin_proto_rawDesc,
|
||||
NumEnums: 4,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_extensions_v1alpha1_wasm_proto_goTypes,
|
||||
DependencyIndexes: file_extensions_v1alpha1_wasm_proto_depIdxs,
|
||||
EnumInfos: file_extensions_v1alpha1_wasm_proto_enumTypes,
|
||||
MessageInfos: file_extensions_v1alpha1_wasm_proto_msgTypes,
|
||||
GoTypes: file_extensions_v1alpha1_wasmplugin_proto_goTypes,
|
||||
DependencyIndexes: file_extensions_v1alpha1_wasmplugin_proto_depIdxs,
|
||||
EnumInfos: file_extensions_v1alpha1_wasmplugin_proto_enumTypes,
|
||||
MessageInfos: file_extensions_v1alpha1_wasmplugin_proto_msgTypes,
|
||||
}.Build()
|
||||
File_extensions_v1alpha1_wasm_proto = out.File
|
||||
file_extensions_v1alpha1_wasm_proto_rawDesc = nil
|
||||
file_extensions_v1alpha1_wasm_proto_goTypes = nil
|
||||
file_extensions_v1alpha1_wasm_proto_depIdxs = nil
|
||||
File_extensions_v1alpha1_wasmplugin_proto = out.File
|
||||
file_extensions_v1alpha1_wasmplugin_proto_rawDesc = nil
|
||||
file_extensions_v1alpha1_wasmplugin_proto_goTypes = nil
|
||||
file_extensions_v1alpha1_wasmplugin_proto_depIdxs = nil
|
||||
}
|
||||
@@ -8,49 +8,49 @@ import (
|
||||
|
||||
// MarshalJSON is a custom marshaler for WasmPlugin
|
||||
func (this *WasmPlugin) MarshalJSON() ([]byte, error) {
|
||||
str, err := WasmMarshaler.MarshalToString(this)
|
||||
str, err := WasmpluginMarshaler.MarshalToString(this)
|
||||
return []byte(str), err
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a custom unmarshaler for WasmPlugin
|
||||
func (this *WasmPlugin) UnmarshalJSON(b []byte) error {
|
||||
return WasmUnmarshaler.Unmarshal(bytes.NewReader(b), this)
|
||||
return WasmpluginUnmarshaler.Unmarshal(bytes.NewReader(b), this)
|
||||
}
|
||||
|
||||
// MarshalJSON is a custom marshaler for MatchRule
|
||||
func (this *MatchRule) MarshalJSON() ([]byte, error) {
|
||||
str, err := WasmMarshaler.MarshalToString(this)
|
||||
str, err := WasmpluginMarshaler.MarshalToString(this)
|
||||
return []byte(str), err
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a custom unmarshaler for MatchRule
|
||||
func (this *MatchRule) UnmarshalJSON(b []byte) error {
|
||||
return WasmUnmarshaler.Unmarshal(bytes.NewReader(b), this)
|
||||
return WasmpluginUnmarshaler.Unmarshal(bytes.NewReader(b), this)
|
||||
}
|
||||
|
||||
// MarshalJSON is a custom marshaler for VmConfig
|
||||
func (this *VmConfig) MarshalJSON() ([]byte, error) {
|
||||
str, err := WasmMarshaler.MarshalToString(this)
|
||||
str, err := WasmpluginMarshaler.MarshalToString(this)
|
||||
return []byte(str), err
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a custom unmarshaler for VmConfig
|
||||
func (this *VmConfig) UnmarshalJSON(b []byte) error {
|
||||
return WasmUnmarshaler.Unmarshal(bytes.NewReader(b), this)
|
||||
return WasmpluginUnmarshaler.Unmarshal(bytes.NewReader(b), this)
|
||||
}
|
||||
|
||||
// MarshalJSON is a custom marshaler for EnvVar
|
||||
func (this *EnvVar) MarshalJSON() ([]byte, error) {
|
||||
str, err := WasmMarshaler.MarshalToString(this)
|
||||
str, err := WasmpluginMarshaler.MarshalToString(this)
|
||||
return []byte(str), err
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a custom unmarshaler for EnvVar
|
||||
func (this *EnvVar) UnmarshalJSON(b []byte) error {
|
||||
return WasmUnmarshaler.Unmarshal(bytes.NewReader(b), this)
|
||||
return WasmpluginUnmarshaler.Unmarshal(bytes.NewReader(b), this)
|
||||
}
|
||||
|
||||
var (
|
||||
WasmMarshaler = &jsonpb.Marshaler{}
|
||||
WasmUnmarshaler = &jsonpb.Unmarshaler{AllowUnknownFields: true}
|
||||
WasmpluginMarshaler = &jsonpb.Marshaler{}
|
||||
WasmpluginUnmarshaler = &jsonpb.Unmarshaler{AllowUnknownFields: true}
|
||||
)
|
||||
@@ -284,6 +284,10 @@ spec:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
protocol:
|
||||
type: string
|
||||
sni:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
zkServicesPath:
|
||||
|
||||
@@ -126,6 +126,8 @@ type RegistryConfig struct {
|
||||
ConsulServiceTag string `protobuf:"bytes,15,opt,name=consulServiceTag,proto3" json:"consulServiceTag,omitempty"`
|
||||
ConsulRefreshInterval int64 `protobuf:"varint,16,opt,name=consulRefreshInterval,proto3" json:"consulRefreshInterval,omitempty"`
|
||||
AuthSecretName string `protobuf:"bytes,17,opt,name=authSecretName,proto3" json:"authSecretName,omitempty"`
|
||||
Protocol string `protobuf:"bytes,18,opt,name=protocol,proto3" json:"protocol,omitempty"`
|
||||
Sni string `protobuf:"bytes,19,opt,name=sni,proto3" json:"sni,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RegistryConfig) Reset() {
|
||||
@@ -279,6 +281,20 @@ func (x *RegistryConfig) GetAuthSecretName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RegistryConfig) GetProtocol() string {
|
||||
if x != nil {
|
||||
return x.Protocol
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RegistryConfig) GetSni() string {
|
||||
if x != nil {
|
||||
return x.Sni
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_networking_v1_mcp_bridge_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_networking_v1_mcp_bridge_proto_rawDesc = []byte{
|
||||
@@ -292,7 +308,7 @@ var file_networking_v1_mcp_bridge_proto_rawDesc = []byte{
|
||||
0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x69, 0x67, 0x72,
|
||||
0x65, 0x73, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xa5, 0x05, 0x0a,
|
||||
0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xd3, 0x05, 0x0a,
|
||||
0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||
0x17, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0,
|
||||
0x41, 0x02, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
||||
@@ -335,10 +351,13 @@ var file_networking_v1_mcp_bridge_proto_rawDesc = []byte{
|
||||
0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e,
|
||||
0x61, 0x75, 0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x11,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68, 0x69, 0x67, 0x72, 0x65,
|
||||
0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e,
|
||||
0x67, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||
0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||
0x12, 0x10, 0x0a, 0x03, 0x73, 0x6e, 0x69, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73,
|
||||
0x6e, 0x69, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73,
|
||||
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2f,
|
||||
0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -64,4 +64,6 @@ message RegistryConfig {
|
||||
string consulServiceTag = 15;
|
||||
int64 consulRefreshInterval = 16;
|
||||
string authSecretName = 17;
|
||||
string protocol = 18;
|
||||
string sni = 19;
|
||||
}
|
||||
|
||||
Submodule envoy/envoy updated: 8c3cc1ac25...b3541845c1
3
go.mod
3
go.mod
@@ -119,7 +119,6 @@ require (
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
@@ -176,13 +175,13 @@ require (
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/prometheus/prometheus v0.45.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.7.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
|
||||
|
||||
3
go.sum
3
go.sum
@@ -1623,7 +1623,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
@@ -1705,6 +1704,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto=
|
||||
github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
|
||||
github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw=
|
||||
github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 2.0.0-rc.1
|
||||
appVersion: 2.0.1
|
||||
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: 2.0.0-rc.1
|
||||
version: 2.0.1
|
||||
|
||||
@@ -284,6 +284,10 @@ spec:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
protocol:
|
||||
type: string
|
||||
sni:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
zkServicesPath:
|
||||
@@ -302,3 +306,4 @@ spec:
|
||||
subresources:
|
||||
status: {}
|
||||
|
||||
---
|
||||
|
||||
@@ -101,3 +101,15 @@ higress: {{ include "controller.name" . }}
|
||||
true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gateway.podMonitor.gvk" -}}
|
||||
{{- if eq .Values.gateway.metrics.provider "monitoring.coreos.com" -}}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
{{- else if eq .Values.gateway.metrics.provider "operator.victoriametrics.com" -}}
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMPodScrape
|
||||
{{- else -}}
|
||||
{{- fail "unexpected gateway.metrics.provider" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
326
helm/core/templates/_pod.tpl
Normal file
326
helm/core/templates/_pod.tpl
Normal file
@@ -0,0 +1,326 @@
|
||||
|
||||
{{/*
|
||||
Rendering the pod template of gateway component.
|
||||
*/}}
|
||||
{{- define "gateway.podTemplate" -}}
|
||||
{{- $o11y := .Values.global.o11y -}}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{- if .Values.global.enableHigressIstio }}
|
||||
"enableHigressIstio": "true"
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.podAnnotations }}
|
||||
{{- toYaml .Values.gateway.podAnnotations | nindent 6 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
sidecar.istio.io/inject: "false"
|
||||
{{- with .Values.gateway.revision }}
|
||||
istio.io/rev: {{ . }}
|
||||
{{- end }}
|
||||
{{- include "gateway.selectorLabels" . | nindent 6 }}
|
||||
spec:
|
||||
{{- with .Values.gateway.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "gateway.serviceAccountName" . }}
|
||||
{{- if .Values.global.priorityClassName }}
|
||||
priorityClassName: "{{ .Values.global.priorityClassName }}"
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- if .Values.gateway.securityContext }}
|
||||
{{- toYaml .Values.gateway.securityContext | nindent 6 }}
|
||||
{{- else if and .Values.gateway.unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
|
||||
sysctls:
|
||||
- name: net.ipv4.ip_unprivileged_port_start
|
||||
value: "0"
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: higress-gateway
|
||||
image: "{{ .Values.gateway.hub | default .Values.global.hub }}/{{ .Values.gateway.image | default "gateway" }}:{{ .Values.gateway.tag | default .Chart.AppVersion }}"
|
||||
args:
|
||||
- proxy
|
||||
- router
|
||||
- --domain
|
||||
- $(POD_NAMESPACE).svc.cluster.local
|
||||
- --proxyLogLevel=warning
|
||||
- --proxyComponentLogLevel=misc:error
|
||||
- --log_output_level=all:info
|
||||
- --serviceCluster=higress-gateway
|
||||
securityContext:
|
||||
{{- if .Values.gateway.containerSecurityContext }}
|
||||
{{- toYaml .Values.gateway.containerSecurityContext | nindent 10 }}
|
||||
{{- else if and .Values.gateway.unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
# When enabling lite metrics, the configuration template files need to be replaced.
|
||||
{{- if not .Values.global.liteMetrics }}
|
||||
readOnlyRootFilesystem: true
|
||||
{{- end }}
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
runAsNonRoot: true
|
||||
{{- else }}
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
runAsUser: 0
|
||||
runAsGroup: 1337
|
||||
runAsNonRoot: false
|
||||
allowPrivilegeEscalation: true
|
||||
{{- end }}
|
||||
env:
|
||||
- name: NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: spec.nodeName
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.namespace
|
||||
- name: INSTANCE_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: status.podIP
|
||||
- name: HOST_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: status.hostIP
|
||||
- name: SERVICE_ACCOUNT
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.serviceAccountName
|
||||
- name: PROXY_XDS_VIA_AGENT
|
||||
value: "true"
|
||||
- name: ENABLE_INGRESS_GATEWAY_SDS
|
||||
value: "false"
|
||||
- name: JWT_POLICY
|
||||
value: {{ include "controller.jwtPolicy" . }}
|
||||
- name: ISTIO_META_HTTP10
|
||||
value: "1"
|
||||
- name: ISTIO_META_CLUSTER_ID
|
||||
value: "{{ $.Values.clusterName | default `Kubernetes` }}"
|
||||
- name: INSTANCE_NAME
|
||||
value: "higress-gateway"
|
||||
{{- if .Values.global.liteMetrics }}
|
||||
- name: LITE_METRICS
|
||||
value: "on"
|
||||
{{- end }}
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- name: ISTIO_BOOTSTRAP_OVERRIDE
|
||||
value: /etc/istio/custom-bootstrap/custom_bootstrap.json
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.networkGateway }}
|
||||
- name: ISTIO_META_REQUESTED_NETWORK_VIEW
|
||||
value: "{{.}}"
|
||||
{{- end }}
|
||||
{{- range $key, $val := .Values.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $val | quote }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: 15020
|
||||
protocol: TCP
|
||||
name: istio-prom
|
||||
- containerPort: 15090
|
||||
protocol: TCP
|
||||
name: http-envoy-prom
|
||||
{{- if or .Values.global.local .Values.global.kind }}
|
||||
- containerPort: {{ .Values.gateway.httpPort }}
|
||||
hostPort: {{ .Values.gateway.httpPort }}
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: {{ .Values.gateway.httpsPort }}
|
||||
hostPort: {{ .Values.gateway.httpsPort }}
|
||||
name: https
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
readinessProbe:
|
||||
failureThreshold: {{ .Values.gateway.readinessFailureThreshold }}
|
||||
httpGet:
|
||||
path: /healthz/ready
|
||||
port: 15021
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: {{ .Values.gateway.readinessInitialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.gateway.readinessPeriodSeconds }}
|
||||
successThreshold: {{ .Values.gateway.readinessSuccessThreshold }}
|
||||
timeoutSeconds: {{ .Values.gateway.readinessTimeoutSeconds }}
|
||||
{{- if not (or .Values.global.local .Values.global.kind) }}
|
||||
resources:
|
||||
{{- toYaml .Values.gateway.resources | nindent 10 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/secrets/workload-spiffe-uds
|
||||
name: workload-socket
|
||||
- mountPath: /var/run/secrets/credential-uds
|
||||
name: credential-socket
|
||||
- mountPath: /var/run/secrets/workload-spiffe-credentials
|
||||
name: workload-certs
|
||||
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
|
||||
- name: istio-token
|
||||
mountPath: /var/run/secrets/tokens
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
- name: config
|
||||
mountPath: /etc/istio/config
|
||||
- name: higress-ca-root-cert
|
||||
mountPath: /var/run/secrets/istio
|
||||
- name: istio-data
|
||||
mountPath: /var/lib/istio/data
|
||||
- name: podinfo
|
||||
mountPath: /etc/istio/pod
|
||||
- name: proxy-socket
|
||||
mountPath: /etc/istio/proxy
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- mountPath: /etc/istio/custom-bootstrap
|
||||
name: custom-bootstrap-volume
|
||||
{{- end }}
|
||||
{{- if .Values.global.volumeWasmPlugins }}
|
||||
- mountPath: /opt/plugins
|
||||
name: local-wasmplugins-volume
|
||||
{{- end }}
|
||||
{{- if $o11y.enabled }}
|
||||
- mountPath: /var/log/proxy
|
||||
name: log
|
||||
{{- end }}
|
||||
{{- if $o11y.enabled }}
|
||||
{{- $config := $o11y.promtail }}
|
||||
- name: promtail
|
||||
image: {{ $config.image.repository }}:{{ $config.image.tag }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- -config.file=/etc/promtail/promtail.yaml
|
||||
env:
|
||||
- name: 'HOSTNAME'
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: 'spec.nodeName'
|
||||
ports:
|
||||
- containerPort: {{ $config.port }}
|
||||
name: http-metrics
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: {{ $config.port }}
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
volumeMounts:
|
||||
- name: promtail-config
|
||||
mountPath: "/etc/promtail"
|
||||
- name: log
|
||||
mountPath: /var/log/proxy
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.hostNetwork }}
|
||||
hostNetwork: {{ .Values.gateway.hostNetwork }}
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: workload-socket
|
||||
- emptyDir: {}
|
||||
name: credential-socket
|
||||
- emptyDir: {}
|
||||
name: workload-certs
|
||||
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
|
||||
- name: istio-token
|
||||
projected:
|
||||
sources:
|
||||
- serviceAccountToken:
|
||||
audience: istio-ca
|
||||
expirationSeconds: 43200
|
||||
path: istio-token
|
||||
{{- end }}
|
||||
- name: higress-ca-root-cert
|
||||
configMap:
|
||||
{{- if .Values.global.enableHigressIstio }}
|
||||
name: istio-ca-root-cert
|
||||
{{- else }}
|
||||
name: higress-ca-root-cert
|
||||
{{- end }}
|
||||
- name: config
|
||||
configMap:
|
||||
name: higress-config
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- configMap:
|
||||
defaultMode: 420
|
||||
name: higress-custom-bootstrap
|
||||
name: custom-bootstrap-volume
|
||||
{{- end }}
|
||||
- name: istio-data
|
||||
emptyDir: {}
|
||||
- name: proxy-socket
|
||||
emptyDir: {}
|
||||
{{- if $o11y.enabled }}
|
||||
- name: log
|
||||
emptyDir: {}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: promtail-config
|
||||
configMap:
|
||||
name: higress-promtail
|
||||
{{- end }}
|
||||
- name: podinfo
|
||||
downwardAPI:
|
||||
defaultMode: 420
|
||||
items:
|
||||
- fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.labels
|
||||
path: labels
|
||||
- fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.annotations
|
||||
path: annotations
|
||||
- path: cpu-request
|
||||
resourceFieldRef:
|
||||
containerName: higress-gateway
|
||||
divisor: 1m
|
||||
resource: requests.cpu
|
||||
- path: cpu-limit
|
||||
resourceFieldRef:
|
||||
containerName: higress-gateway
|
||||
divisor: 1m
|
||||
resource: limits.cpu
|
||||
{{- if .Values.global.volumeWasmPlugins }}
|
||||
- name: local-wasmplugins-volume
|
||||
hostPath:
|
||||
path: /opt/plugins
|
||||
type: Directory
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
@@ -155,44 +155,12 @@ data:
|
||||
"transport_api_version": "V3",
|
||||
"grpc_service": {
|
||||
"envoy_grpc": {
|
||||
"cluster_name": "service_skywalking"
|
||||
"cluster_name": "outbound|{{ .Values.tracing.skywalking.port }}||{{ .Values.tracing.skywalking.service }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
{
|
||||
"name": "service_skywalking",
|
||||
"type": "LOGICAL_DNS",
|
||||
"connect_timeout": "5s",
|
||||
"http2_protocol_options": {
|
||||
},
|
||||
"dns_lookup_family": "V4_ONLY",
|
||||
"lb_policy": "ROUND_ROBIN",
|
||||
"load_assignment": {
|
||||
"cluster_name": "service_skywalking",
|
||||
"endpoints": [
|
||||
{
|
||||
"lb_endpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socket_address": {
|
||||
"address": "{{ .Values.tracing.skywalking.service }}",
|
||||
"port_value": "{{ .Values.tracing.skywalking.port }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
---
|
||||
{{- end }}
|
||||
|
||||
@@ -133,6 +133,8 @@ spec:
|
||||
env:
|
||||
- name: PILOT_ENABLE_QUIC_LISTENERS
|
||||
value: "true"
|
||||
- name: VALIDATION_WEBHOOK_CONFIG_NAME
|
||||
value: ""
|
||||
- name: ISTIO_DUAL_STACK
|
||||
value: "{{ .Values.global.enableIPv6 }}"
|
||||
- name: PILOT_ENABLE_HEADLESS_SERVICE_POD_LISTENERS
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
{{- if $kernelVersion }}
|
||||
{{- $kernelVersion = regexFind "^(\\d+\\.\\d+\\.\\d+)" $kernelVersion }}
|
||||
{{- if and $kernelVersion (semverCompare "<4.11.0" $kernelVersion) }}
|
||||
{{- $unprivilegedPortSupported = false }}
|
||||
{{- $unprivilegedPortSupported = false }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
{{- $_ := set .Values.gateway "unprivilegedPortSupported" $unprivilegedPortSupported -}}
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
@@ -23,310 +25,5 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "gateway.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{- if .Values.global.enableHigressIstio }}
|
||||
"enableHigressIstio": "true"
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.podAnnotations }}
|
||||
{{- toYaml .Values.gateway.podAnnotations | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
sidecar.istio.io/inject: "false"
|
||||
{{- with .Values.gateway.revision }}
|
||||
istio.io/rev: {{ . }}
|
||||
{{- end }}
|
||||
{{- include "gateway.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.gateway.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "gateway.serviceAccountName" . }}
|
||||
{{- if .Values.global.priorityClassName }}
|
||||
priorityClassName: "{{ .Values.global.priorityClassName }}"
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- if .Values.gateway.securityContext }}
|
||||
{{- toYaml .Values.gateway.securityContext | nindent 8 }}
|
||||
{{- else if and $unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
|
||||
sysctls:
|
||||
- name: net.ipv4.ip_unprivileged_port_start
|
||||
value: "0"
|
||||
{{- end }}
|
||||
containers:
|
||||
{{- if $o11y.enabled }}
|
||||
{{- $config := $o11y.promtail }}
|
||||
- name: promtail
|
||||
image: {{ $config.image.repository }}:{{ $config.image.tag }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- -config.file=/etc/promtail/promtail.yaml
|
||||
env:
|
||||
- name: 'HOSTNAME'
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: 'spec.nodeName'
|
||||
ports:
|
||||
- containerPort: {{ $config.port }}
|
||||
name: http-metrics
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: {{ $config.port }}
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
volumeMounts:
|
||||
- name: promtail-config
|
||||
mountPath: "/etc/promtail"
|
||||
- name: log
|
||||
mountPath: /var/log/proxy
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
{{- end }}
|
||||
- name: higress-gateway
|
||||
image: "{{ .Values.gateway.hub | default .Values.global.hub }}/{{ .Values.gateway.image | default "gateway" }}:{{ .Values.gateway.tag | default .Chart.AppVersion }}"
|
||||
args:
|
||||
- proxy
|
||||
- router
|
||||
- --domain
|
||||
- $(POD_NAMESPACE).svc.cluster.local
|
||||
- --proxyLogLevel=warning
|
||||
- --proxyComponentLogLevel=misc:error
|
||||
- --log_output_level=all:info
|
||||
- --serviceCluster=higress-gateway
|
||||
securityContext:
|
||||
{{- if .Values.gateway.containerSecurityContext }}
|
||||
{{- toYaml .Values.gateway.containerSecurityContext | nindent 12 }}
|
||||
{{- else if and $unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
# When enabling lite metrics, the configuration template files need to be replaced.
|
||||
{{- if not .Values.global.liteMetrics }}
|
||||
readOnlyRootFilesystem: true
|
||||
{{- end }}
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
runAsNonRoot: true
|
||||
{{- else }}
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
runAsUser: 0
|
||||
runAsGroup: 1337
|
||||
runAsNonRoot: false
|
||||
allowPrivilegeEscalation: true
|
||||
{{- end }}
|
||||
env:
|
||||
- name: NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: spec.nodeName
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.namespace
|
||||
- name: INSTANCE_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: status.podIP
|
||||
- name: HOST_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: status.hostIP
|
||||
- name: SERVICE_ACCOUNT
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.serviceAccountName
|
||||
- name: PILOT_XDS_SEND_TIMEOUT
|
||||
value: 60s
|
||||
- name: PROXY_XDS_VIA_AGENT
|
||||
value: "true"
|
||||
- name: ENABLE_INGRESS_GATEWAY_SDS
|
||||
value: "false"
|
||||
- name: JWT_POLICY
|
||||
value: {{ include "controller.jwtPolicy" . }}
|
||||
- name: ISTIO_META_HTTP10
|
||||
value: "1"
|
||||
- name: ISTIO_META_CLUSTER_ID
|
||||
value: "{{ $.Values.clusterName | default `Kubernetes` }}"
|
||||
- name: INSTANCE_NAME
|
||||
value: "higress-gateway"
|
||||
{{- if .Values.global.liteMetrics }}
|
||||
- name: LITE_METRICS
|
||||
value: "on"
|
||||
{{- end }}
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- name: ISTIO_BOOTSTRAP_OVERRIDE
|
||||
value: /etc/istio/custom-bootstrap/custom_bootstrap.json
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.networkGateway }}
|
||||
- name: ISTIO_META_REQUESTED_NETWORK_VIEW
|
||||
value: "{{.}}"
|
||||
{{- end }}
|
||||
{{- range $key, $val := .Values.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $val | quote }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: 15090
|
||||
protocol: TCP
|
||||
name: http-envoy-prom
|
||||
{{- if or .Values.global.local .Values.global.kind }}
|
||||
- containerPort: {{ .Values.gateway.httpPort }}
|
||||
hostPort: {{ .Values.gateway.httpPort }}
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: {{ .Values.gateway.httpsPort }}
|
||||
hostPort: {{ .Values.gateway.httpsPort }}
|
||||
name: https
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
readinessProbe:
|
||||
failureThreshold: {{ .Values.gateway.readinessFailureThreshold }}
|
||||
httpGet:
|
||||
path: /healthz/ready
|
||||
port: 15021
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: {{ .Values.gateway.readinessInitialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.gateway.readinessPeriodSeconds }}
|
||||
successThreshold: {{ .Values.gateway.readinessSuccessThreshold }}
|
||||
timeoutSeconds: {{ .Values.gateway.readinessTimeoutSeconds }}
|
||||
{{- if not (or .Values.global.local .Values.global.kind) }}
|
||||
resources:
|
||||
{{- toYaml .Values.gateway.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
|
||||
- name: istio-token
|
||||
mountPath: /var/run/secrets/tokens
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
- name: config
|
||||
mountPath: /etc/istio/config
|
||||
- name: istio-ca-root-cert
|
||||
mountPath: /var/run/secrets/istio
|
||||
- name: istio-data
|
||||
mountPath: /var/lib/istio/data
|
||||
- name: podinfo
|
||||
mountPath: /etc/istio/pod
|
||||
- name: proxy-socket
|
||||
mountPath: /etc/istio/proxy
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- mountPath: /etc/istio/custom-bootstrap
|
||||
name: custom-bootstrap-volume
|
||||
{{- end }}
|
||||
{{- if .Values.global.volumeWasmPlugins }}
|
||||
- mountPath: /opt/plugins
|
||||
name: local-wasmplugins-volume
|
||||
{{- end }}
|
||||
{{- if $o11y.enabled }}
|
||||
- mountPath: /var/log/proxy
|
||||
name: log
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.hostNetwork }}
|
||||
hostNetwork: {{ .Values.gateway.hostNetwork }}
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
|
||||
- name: istio-token
|
||||
projected:
|
||||
sources:
|
||||
- serviceAccountToken:
|
||||
audience: istio-ca
|
||||
expirationSeconds: 43200
|
||||
path: istio-token
|
||||
{{- end }}
|
||||
- name: istio-ca-root-cert
|
||||
configMap:
|
||||
{{- if .Values.global.enableHigressIstio }}
|
||||
name: istio-ca-root-cert
|
||||
{{- else }}
|
||||
name: higress-ca-root-cert
|
||||
{{- end }}
|
||||
- name: config
|
||||
configMap:
|
||||
name: higress-config
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- configMap:
|
||||
defaultMode: 420
|
||||
name: higress-custom-bootstrap
|
||||
name: custom-bootstrap-volume
|
||||
{{- end }}
|
||||
- name: istio-data
|
||||
emptyDir: {}
|
||||
- name: proxy-socket
|
||||
emptyDir: {}
|
||||
{{- if $o11y.enabled }}
|
||||
- name: log
|
||||
emptyDir: {}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: promtail-config
|
||||
configMap:
|
||||
name: higress-promtail
|
||||
{{- end }}
|
||||
- name: podinfo
|
||||
downwardAPI:
|
||||
defaultMode: 420
|
||||
items:
|
||||
- fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.labels
|
||||
path: labels
|
||||
- fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.annotations
|
||||
path: annotations
|
||||
- path: cpu-request
|
||||
resourceFieldRef:
|
||||
containerName: higress-gateway
|
||||
divisor: 1m
|
||||
resource: requests.cpu
|
||||
- path: cpu-limit
|
||||
resourceFieldRef:
|
||||
containerName: higress-gateway
|
||||
divisor: 1m
|
||||
resource: limits.cpu
|
||||
{{- if .Values.global.volumeWasmPlugins }}
|
||||
- name: local-wasmplugins-volume
|
||||
hostPath:
|
||||
path: /opt/plugins
|
||||
type: Directory
|
||||
{{- end }}
|
||||
{{- include "gateway.podTemplate" $ | nindent 2 -}}
|
||||
{{- end }}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
{{- if eq .Values.gateway.kind "Deployment" -}}
|
||||
{{- $o11y := .Values.global.o11y }}
|
||||
{{- $unprivilegedPortSupported := true }}
|
||||
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
|
||||
{{- $kernelVersion := $node.status.nodeInfo.kernelVersion }}
|
||||
{{- if $kernelVersion }}
|
||||
{{- $kernelVersion = regexFind "^(\\d+\\.\\d+\\.\\d+)" $kernelVersion }}
|
||||
{{- if and $kernelVersion (semverCompare "<4.11.0" $kernelVersion) }}
|
||||
{{- $unprivilegedPortSupported = false }}
|
||||
{{- $unprivilegedPortSupported = false }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
{{- $_ := set .Values.gateway "unprivilegedPortSupported" $unprivilegedPortSupported -}}
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -38,311 +39,7 @@ spec:
|
||||
{{- else }}
|
||||
maxUnavailable: {{ .Values.gateway.rollingMaxUnavailable }}
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{- if .Values.global.enableHigressIstio }}
|
||||
"enableHigressIstio": "true"
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.podAnnotations }}
|
||||
{{- toYaml .Values.gateway.podAnnotations | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
sidecar.istio.io/inject: "false"
|
||||
{{- with .Values.gateway.revision }}
|
||||
istio.io/rev: {{ . }}
|
||||
{{- end }}
|
||||
{{- include "gateway.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.gateway.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "gateway.serviceAccountName" . }}
|
||||
{{- if .Values.global.priorityClassName }}
|
||||
priorityClassName: "{{ .Values.global.priorityClassName }}"
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- if .Values.gateway.securityContext }}
|
||||
{{- toYaml .Values.gateway.securityContext | nindent 8 }}
|
||||
{{- else if and $unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
|
||||
sysctls:
|
||||
- name: net.ipv4.ip_unprivileged_port_start
|
||||
value: "0"
|
||||
{{- end }}
|
||||
containers:
|
||||
{{- if $o11y.enabled }}
|
||||
{{- $config := $o11y.promtail }}
|
||||
- name: promtail
|
||||
image: {{ $config.image.repository }}:{{ $config.image.tag }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- -config.file=/etc/promtail/promtail.yaml
|
||||
env:
|
||||
- name: 'HOSTNAME'
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: 'spec.nodeName'
|
||||
ports:
|
||||
- containerPort: {{ $config.port }}
|
||||
name: http-metrics
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: {{ $config.port }}
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
volumeMounts:
|
||||
- name: promtail-config
|
||||
mountPath: "/etc/promtail"
|
||||
- name: log
|
||||
mountPath: /var/log/proxy
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
{{- end }}
|
||||
- name: higress-gateway
|
||||
image: "{{ .Values.gateway.hub | default .Values.global.hub }}/{{ .Values.gateway.image | default "gateway" }}:{{ .Values.gateway.tag | default .Chart.AppVersion }}"
|
||||
args:
|
||||
- proxy
|
||||
- router
|
||||
- --domain
|
||||
- $(POD_NAMESPACE).svc.cluster.local
|
||||
- --proxyLogLevel=warning
|
||||
- --proxyComponentLogLevel=misc:error
|
||||
- --log_output_level=all:info
|
||||
- --serviceCluster=higress-gateway
|
||||
securityContext:
|
||||
{{- if .Values.gateway.containerSecurityContext }}
|
||||
{{- toYaml .Values.gateway.containerSecurityContext | nindent 12 }}
|
||||
{{- else if and $unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
# When enabling lite metrics, the configuration template files need to be replaced.
|
||||
{{- if not .Values.global.liteMetrics }}
|
||||
readOnlyRootFilesystem: true
|
||||
{{- end }}
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
runAsNonRoot: true
|
||||
{{- else }}
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
runAsUser: 0
|
||||
runAsGroup: 1337
|
||||
runAsNonRoot: false
|
||||
allowPrivilegeEscalation: true
|
||||
{{- end }}
|
||||
env:
|
||||
- name: NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: spec.nodeName
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.namespace
|
||||
- name: INSTANCE_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: status.podIP
|
||||
- name: HOST_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: status.hostIP
|
||||
- name: SERVICE_ACCOUNT
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.serviceAccountName
|
||||
- name: PROXY_XDS_VIA_AGENT
|
||||
value: "true"
|
||||
- name: ENABLE_INGRESS_GATEWAY_SDS
|
||||
value: "false"
|
||||
- name: JWT_POLICY
|
||||
value: {{ include "controller.jwtPolicy" . }}
|
||||
- name: ISTIO_META_HTTP10
|
||||
value: "1"
|
||||
- name: ISTIO_META_CLUSTER_ID
|
||||
value: "{{ $.Values.clusterName | default `Kubernetes` }}"
|
||||
- name: INSTANCE_NAME
|
||||
value: "higress-gateway"
|
||||
{{- if .Values.global.liteMetrics }}
|
||||
- name: LITE_METRICS
|
||||
value: "on"
|
||||
{{- end }}
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- name: ISTIO_BOOTSTRAP_OVERRIDE
|
||||
value: /etc/istio/custom-bootstrap/custom_bootstrap.json
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.networkGateway }}
|
||||
- name: ISTIO_META_REQUESTED_NETWORK_VIEW
|
||||
value: "{{.}}"
|
||||
{{- end }}
|
||||
{{- range $key, $val := .Values.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $val | quote }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: 15020
|
||||
protocol: TCP
|
||||
name: istio-prom
|
||||
- containerPort: 15090
|
||||
protocol: TCP
|
||||
name: http-envoy-prom
|
||||
{{- if or .Values.global.local .Values.global.kind }}
|
||||
- containerPort: {{ .Values.gateway.httpPort }}
|
||||
hostPort: {{ .Values.gateway.httpPort }}
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: {{ .Values.gateway.httpsPort }}
|
||||
hostPort: {{ .Values.gateway.httpsPort }}
|
||||
name: https
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
readinessProbe:
|
||||
failureThreshold: {{ .Values.gateway.readinessFailureThreshold }}
|
||||
httpGet:
|
||||
path: /healthz/ready
|
||||
port: 15021
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: {{ .Values.gateway.readinessInitialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.gateway.readinessPeriodSeconds }}
|
||||
successThreshold: {{ .Values.gateway.readinessSuccessThreshold }}
|
||||
timeoutSeconds: {{ .Values.gateway.readinessTimeoutSeconds }}
|
||||
{{- if not (or .Values.global.local .Values.global.kind) }}
|
||||
resources:
|
||||
{{- toYaml .Values.gateway.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
|
||||
- name: istio-token
|
||||
mountPath: /var/run/secrets/tokens
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
- name: config
|
||||
mountPath: /etc/istio/config
|
||||
- name: istio-ca-root-cert
|
||||
mountPath: /var/run/secrets/istio
|
||||
- name: istio-data
|
||||
mountPath: /var/lib/istio/data
|
||||
- name: podinfo
|
||||
mountPath: /etc/istio/pod
|
||||
- name: proxy-socket
|
||||
mountPath: /etc/istio/proxy
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- mountPath: /etc/istio/custom-bootstrap
|
||||
name: custom-bootstrap-volume
|
||||
{{- end }}
|
||||
{{- if .Values.global.volumeWasmPlugins }}
|
||||
- mountPath: /opt/plugins
|
||||
name: local-wasmplugins-volume
|
||||
{{- end }}
|
||||
{{- if $o11y.enabled }}
|
||||
- mountPath: /var/log/proxy
|
||||
name: log
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.hostNetwork }}
|
||||
hostNetwork: {{ .Values.gateway.hostNetwork }}
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
|
||||
- name: istio-token
|
||||
projected:
|
||||
sources:
|
||||
- serviceAccountToken:
|
||||
audience: istio-ca
|
||||
expirationSeconds: 43200
|
||||
path: istio-token
|
||||
{{- end }}
|
||||
- name: istio-ca-root-cert
|
||||
configMap:
|
||||
{{- if .Values.global.enableHigressIstio }}
|
||||
name: istio-ca-root-cert
|
||||
{{- else }}
|
||||
name: higress-ca-root-cert
|
||||
{{- end }}
|
||||
- name: config
|
||||
configMap:
|
||||
name: higress-config
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- configMap:
|
||||
defaultMode: 420
|
||||
name: higress-custom-bootstrap
|
||||
name: custom-bootstrap-volume
|
||||
{{- end }}
|
||||
- name: istio-data
|
||||
emptyDir: {}
|
||||
- name: proxy-socket
|
||||
emptyDir: {}
|
||||
{{- if $o11y.enabled }}
|
||||
- name: log
|
||||
emptyDir: {}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: promtail-config
|
||||
configMap:
|
||||
name: higress-promtail
|
||||
{{- end }}
|
||||
- name: podinfo
|
||||
downwardAPI:
|
||||
defaultMode: 420
|
||||
items:
|
||||
- fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.labels
|
||||
path: labels
|
||||
- fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.annotations
|
||||
path: annotations
|
||||
- path: cpu-request
|
||||
resourceFieldRef:
|
||||
containerName: higress-gateway
|
||||
divisor: 1m
|
||||
resource: requests.cpu
|
||||
- path: cpu-limit
|
||||
resourceFieldRef:
|
||||
containerName: higress-gateway
|
||||
divisor: 1m
|
||||
resource: limits.cpu
|
||||
{{- if .Values.global.volumeWasmPlugins }}
|
||||
- name: local-wasmplugins-volume
|
||||
hostPath:
|
||||
path: /opt/plugins
|
||||
type: Directory
|
||||
{{- end }}
|
||||
|
||||
{{- include "gateway.podTemplate" $ | nindent 2 -}}
|
||||
|
||||
{{- end }}
|
||||
|
||||
45
helm/core/templates/podmonitor.yaml
Normal file
45
helm/core/templates/podmonitor.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
{{- if .Values.gateway.metrics.enabled }}
|
||||
{{- include "gateway.podMonitor.gvk" . }}
|
||||
metadata:
|
||||
name: {{ printf "%s-metrics" (include "gateway.name" .) | trunc 63 | trimSuffix "-" }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "gateway.labels" . | nindent 4}}
|
||||
annotations:
|
||||
{{- .Values.gateway.annotations | toYaml | nindent 4 }}
|
||||
spec:
|
||||
jobLabel: "app.kubernetes.io/name"
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "gateway.selectorLabels" . | nindent 6 }}
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- {{ .Release.Namespace }}
|
||||
podMetricsEndpoints:
|
||||
- port: istio-prom
|
||||
path: /stats/prometheus
|
||||
{{- if .Values.gateway.metrics.interval }}
|
||||
interval: {{ .Values.gateway.metrics.interval }}
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.metrics.scrapeTimeout }}
|
||||
scrapeTimeout: {{ .Values.gateway.metrics.scrapeTimeout }}
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.metrics.honorLabels }}
|
||||
honorLabels: {{ .Values.gateway.metrics.honorLabels }}
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.metrics.metricRelabelings }}
|
||||
metricRelabelings: {{ toYaml .Values.gateway.metrics.metricRelabelings | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.metrics.relabelings }}
|
||||
relabelings: {{ toYaml .Values.gateway.metrics.relabelings | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.metrics.metricRelabelConfigs }}
|
||||
metricRelabelings: {{ toYaml .Values.gateway.metrics.metricRelabelConfigs | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.metrics.relabelConfigs }}
|
||||
relabelings: {{ toYaml .Values.gateway.metrics.relabelConfigs | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if $.Values.gateway.metrics.rawSpec }}
|
||||
{{- $.Values.gateway.metrics.rawSpec | toYaml | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -24,6 +24,9 @@ spec:
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.service.externalTrafficPolicy }}
|
||||
externalTrafficPolicy: "{{ . }}"
|
||||
{{- end }}
|
||||
{{- with .Values.gateway.service.loadBalancerClass}}
|
||||
loadBalancerClass: "{{ . }}"
|
||||
{{- end }}
|
||||
type: {{ .Values.gateway.service.type }}
|
||||
ports:
|
||||
|
||||
@@ -136,7 +136,6 @@ global:
|
||||
excludeInboundPorts: ""
|
||||
includeInboundPorts: "*"
|
||||
|
||||
|
||||
# istio egress capture allowlist
|
||||
# https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly
|
||||
# example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16"
|
||||
@@ -322,8 +321,8 @@ global:
|
||||
# Host:Port for submitting traces to the Datadog agent.
|
||||
address: "$(HOST_IP):8126"
|
||||
lightstep:
|
||||
address: "" # example: lightstep-satellite:443
|
||||
accessToken: "" # example: abcdefg1234567
|
||||
address: "" # example: lightstep-satellite:443
|
||||
accessToken: "" # example: abcdefg1234567
|
||||
stackdriver:
|
||||
# enables trace output to stdout.
|
||||
debug: false
|
||||
@@ -449,25 +448,25 @@ gateway:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/path: "/stats/prometheus"
|
||||
sidecar.istio.io/inject: "false"
|
||||
|
||||
|
||||
# Define the security context for the pod.
|
||||
# If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443.
|
||||
# On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl.
|
||||
securityContext: ~
|
||||
containerSecurityContext: ~
|
||||
|
||||
|
||||
service:
|
||||
# Type of service. Set to "None" to disable the service entirely
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: http2
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
- name: https
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: 443
|
||||
- name: http2
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
- name: https
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: 443
|
||||
annotations: {}
|
||||
loadBalancerIP: ""
|
||||
loadBalancerClass: ""
|
||||
@@ -476,7 +475,7 @@ gateway:
|
||||
|
||||
rollingMaxSurge: 100%
|
||||
rollingMaxUnavailable: 25%
|
||||
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 2000m
|
||||
@@ -484,22 +483,39 @@ gateway:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 2048Mi
|
||||
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
targetCPUUtilizationPercentage: 80
|
||||
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
|
||||
tolerations: []
|
||||
|
||||
|
||||
affinity: {}
|
||||
|
||||
|
||||
# If specified, the gateway will act as a network gateway for the given network.
|
||||
networkGateway: ""
|
||||
|
||||
|
||||
metrics:
|
||||
# If true, create PodMonitor or VMPodScrape for gateway
|
||||
enabled: false
|
||||
# provider group name for CustomResourceDefinition, can be monitoring.coreos.com or operator.victoriametrics.com
|
||||
provider: monitoring.coreos.com
|
||||
interval: ""
|
||||
scrapeTimeout: ""
|
||||
honorLabels: false
|
||||
# for monitoring.coreos.com/v1.PodMonitor
|
||||
metricRelabelings: []
|
||||
relabelings: []
|
||||
# for operator.victoriametrics.com/v1beta1.VMPodScrape
|
||||
metricRelabelConfigs: []
|
||||
relabelConfigs: []
|
||||
# some more raw podMetricsEndpoints spec
|
||||
rawSpec: {}
|
||||
|
||||
controller:
|
||||
name: "higress-controller"
|
||||
replicas: 1
|
||||
@@ -510,22 +526,20 @@ controller:
|
||||
env: {}
|
||||
|
||||
labels: {}
|
||||
|
||||
probe: {
|
||||
httpGet: {
|
||||
path: /ready,
|
||||
port: 8888,
|
||||
},
|
||||
initialDelaySeconds: 1,
|
||||
periodSeconds: 3,
|
||||
timeoutSeconds: 5
|
||||
}
|
||||
|
||||
|
||||
probe:
|
||||
{
|
||||
httpGet: { path: /ready, port: 8888 },
|
||||
initialDelaySeconds: 1,
|
||||
periodSeconds: 3,
|
||||
timeoutSeconds: 5,
|
||||
}
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
rbac:
|
||||
create: true
|
||||
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
@@ -534,37 +548,30 @@ controller:
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
|
||||
podSecurityContext:
|
||||
{}
|
||||
# fsGroup: 2000
|
||||
|
||||
ports: [
|
||||
{
|
||||
"name": "http",
|
||||
"protocol": "TCP",
|
||||
"port": 8888,
|
||||
"targetPort": 8888,
|
||||
},
|
||||
{
|
||||
"name": "http-solver",
|
||||
"protocol": "TCP",
|
||||
"port": 8889,
|
||||
"targetPort": 8889,
|
||||
},
|
||||
{
|
||||
"name": "grpc",
|
||||
"protocol": "TCP",
|
||||
"port": 15051,
|
||||
"targetPort": 15051,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
ports:
|
||||
[
|
||||
{ "name": "http", "protocol": "TCP", "port": 8888, "targetPort": 8888 },
|
||||
{
|
||||
"name": "http-solver",
|
||||
"protocol": "TCP",
|
||||
"port": 8889,
|
||||
"targetPort": 8889,
|
||||
},
|
||||
{ "name": "grpc", "protocol": "TCP", "port": 15051, "targetPort": 15051 },
|
||||
]
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
||||
securityContext: {}
|
||||
|
||||
securityContext:
|
||||
{}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
@@ -579,11 +586,11 @@ controller:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2048Mi
|
||||
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
|
||||
tolerations: []
|
||||
|
||||
|
||||
affinity: {}
|
||||
|
||||
autoscaling:
|
||||
@@ -594,7 +601,7 @@ controller:
|
||||
automaticHttps:
|
||||
enabled: true
|
||||
email: ""
|
||||
|
||||
|
||||
## Discovery Settings
|
||||
pilot:
|
||||
autoscaleEnabled: false
|
||||
@@ -656,7 +663,6 @@ pilot:
|
||||
# Additional labels to apply to the deployment.
|
||||
deploymentLabels: {}
|
||||
|
||||
|
||||
## Mesh config settings
|
||||
|
||||
# Install the mesh config map, generated from values.yaml.
|
||||
@@ -666,16 +672,15 @@ pilot:
|
||||
# Additional labels to apply on the pod level for monitoring and logging configuration.
|
||||
podLabels: {}
|
||||
|
||||
|
||||
# Tracing config settings
|
||||
tracing:
|
||||
enable: false
|
||||
sampling: 100
|
||||
timeout: 500
|
||||
skywalking:
|
||||
# access_token: ""
|
||||
service: ""
|
||||
port: 11800
|
||||
# access_token: ""
|
||||
service: ""
|
||||
port: 11800
|
||||
# zipkin:
|
||||
# service: ""
|
||||
# port: 9411
|
||||
# service: ""
|
||||
# port: 9411
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
dependencies:
|
||||
- name: higress-core
|
||||
repository: file://../core
|
||||
version: 2.0.0-rc.1
|
||||
version: 2.0.1
|
||||
- name: higress-console
|
||||
repository: https://higress.io/helm-charts/
|
||||
version: 1.4.2
|
||||
digest: sha256:10375e19aff1cc31e4d450ee5c3124ed684c8bcd2f4c019ea88abf7e3d381d76
|
||||
generated: "2024-08-15T19:39:04.526398+08:00"
|
||||
version: 1.4.4
|
||||
digest: sha256:6e4d77c31c834a404a728ec5a8379dd5df27a7e9b998a08e6524dc6534b07c1d
|
||||
generated: "2024-10-09T20:07:21.857942+08:00"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 2.0.0-rc.1
|
||||
appVersion: 2.0.1
|
||||
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: 2.0.0-rc.1
|
||||
version: 2.0.1
|
||||
- name: higress-console
|
||||
repository: "https://higress.io/helm-charts/"
|
||||
version: 1.4.2
|
||||
version: 1.4.4
|
||||
type: application
|
||||
version: 2.0.0-rc.1
|
||||
version: 2.0.1
|
||||
|
||||
@@ -114,6 +114,8 @@ static_resources:
|
||||
value: |
|
||||
{{ .JSONExample }}
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
clusters:
|
||||
- name: httpbin
|
||||
connect_timeout: 30s
|
||||
|
||||
Submodule istio/istio updated: c4fa0ca175...d380470e53
Submodule istio/proxy updated: 9189360b8e...2a5416fcfa
@@ -21,7 +21,10 @@ type Protocol string
|
||||
const (
|
||||
TCP Protocol = "TCP"
|
||||
HTTP Protocol = "HTTP"
|
||||
HTTP2 Protocol = "HTTP2"
|
||||
HTTPS Protocol = "HTTPS"
|
||||
GRPC Protocol = "GRPC"
|
||||
GRPCS Protocol = "GRPCS"
|
||||
Dubbo Protocol = "Dubbo"
|
||||
Unsupported Protocol = "UnsupportedProtocol"
|
||||
)
|
||||
@@ -32,8 +35,14 @@ func ParseProtocol(s string) Protocol {
|
||||
return TCP
|
||||
case "http":
|
||||
return HTTP
|
||||
case "https":
|
||||
return HTTPS
|
||||
case "http2":
|
||||
return HTTP2
|
||||
case "grpc", "triple", "tri":
|
||||
return GRPC
|
||||
case "grpcs":
|
||||
return GRPCS
|
||||
case "dubbo":
|
||||
return Dubbo
|
||||
}
|
||||
@@ -51,7 +60,7 @@ func (p Protocol) IsTCP() bool {
|
||||
|
||||
func (p Protocol) IsHTTP() bool {
|
||||
switch p {
|
||||
case HTTP, GRPC:
|
||||
case HTTP, GRPC, GRPCS, HTTP2, HTTPS:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -60,7 +69,16 @@ func (p Protocol) IsHTTP() bool {
|
||||
|
||||
func (p Protocol) IsGRPC() bool {
|
||||
switch p {
|
||||
case GRPC:
|
||||
case GRPC, GRPCS:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (i Protocol) IsHTTPS() bool {
|
||||
switch i {
|
||||
case HTTPS, GRPCS:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -23,3 +23,7 @@ const KnativeIngressCRDName = "ingresses.networking.internal.knative.dev"
|
||||
const KnativeServicesCRDName = "services.serving.knative.dev"
|
||||
|
||||
const ManagedGatewayController = "higress.io/gateway-controller"
|
||||
|
||||
const RegistryTypeLabelKey = "higress-registry-type"
|
||||
|
||||
const RegistryNameLabelKey = "higress-registry-name"
|
||||
|
||||
@@ -53,6 +53,7 @@ import (
|
||||
extlisterv1 "github.com/alibaba/higress/client/pkg/listers/extensions/v1alpha1"
|
||||
netlisterv1 "github.com/alibaba/higress/client/pkg/listers/networking/v1"
|
||||
"github.com/alibaba/higress/pkg/cert"
|
||||
higressconst "github.com/alibaba/higress/pkg/config/constants"
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/common"
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/configmap"
|
||||
@@ -628,8 +629,8 @@ func (m *IngressConfig) convertServiceEntry([]common.WrapperConfig) []config.Con
|
||||
if m.RegistryReconciler == nil {
|
||||
return nil
|
||||
}
|
||||
serviceEntries := m.RegistryReconciler.GetAllServiceEntryWrapper()
|
||||
IngressLog.Infof("Found http2rpc serviceEntries %s", serviceEntries)
|
||||
serviceEntries := m.RegistryReconciler.GetAllServiceWrapper()
|
||||
IngressLog.Infof("Found mcp serviceEntries %v", serviceEntries)
|
||||
out := make([]config.Config, 0, len(serviceEntries))
|
||||
for _, se := range serviceEntries {
|
||||
out = append(out, config.Config{
|
||||
@@ -638,6 +639,10 @@ func (m *IngressConfig) convertServiceEntry([]common.WrapperConfig) []config.Con
|
||||
Name: se.ServiceEntry.Hosts[0],
|
||||
Namespace: "mcp",
|
||||
CreationTimestamp: se.GetCreateTime(),
|
||||
Labels: map[string]string{
|
||||
higressconst.RegistryTypeLabelKey: se.RegistryType,
|
||||
higressconst.RegistryNameLabelKey: se.RegistryName,
|
||||
},
|
||||
},
|
||||
Spec: se.ServiceEntry,
|
||||
})
|
||||
@@ -703,6 +708,32 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [
|
||||
destinationRules[serviceName] = dr
|
||||
}
|
||||
|
||||
if m.RegistryReconciler != nil {
|
||||
drws := m.RegistryReconciler.GetAllDestinationRuleWrapper()
|
||||
IngressLog.Infof("Found mcp destinationRules: %v", drws)
|
||||
for _, destinationRuleWrapper := range drws {
|
||||
serviceName := destinationRuleWrapper.ServiceKey.ServiceFQDN
|
||||
dr, exist := destinationRules[serviceName]
|
||||
if !exist {
|
||||
destinationRules[serviceName] = destinationRuleWrapper
|
||||
} else if dr.DestinationRule.TrafficPolicy != nil {
|
||||
portTrafficPolicy := destinationRuleWrapper.DestinationRule.TrafficPolicy.PortLevelSettings[0]
|
||||
portUpdated := false
|
||||
for _, portTrafficPolicy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings {
|
||||
if portTrafficPolicy.Port.Number == portTrafficPolicy.Port.Number {
|
||||
portTrafficPolicy.Tls = portTrafficPolicy.Tls
|
||||
portUpdated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if portUpdated {
|
||||
continue
|
||||
}
|
||||
dr.DestinationRule.TrafficPolicy.PortLevelSettings = append(dr.DestinationRule.TrafficPolicy.PortLevelSettings, portTrafficPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]config.Config, 0, len(destinationRules))
|
||||
for _, dr := range destinationRules {
|
||||
sort.SliceStable(dr.DestinationRule.TrafficPolicy.PortLevelSettings, func(i, j int) bool {
|
||||
@@ -727,6 +758,7 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [
|
||||
Spec: dr.DestinationRule,
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -1034,16 +1066,27 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN
|
||||
}
|
||||
if m.RegistryReconciler == nil {
|
||||
m.RegistryReconciler = reconcile.NewReconciler(func() {
|
||||
metadata := config.Meta{
|
||||
seMetadata := config.Meta{
|
||||
Name: "mcpbridge-serviceentry",
|
||||
Namespace: m.namespace,
|
||||
GroupVersionKind: gvk.ServiceEntry,
|
||||
// Set this label so that we do not compare configs and just push.
|
||||
Labels: map[string]string{constants.AlwaysPushLabel: "true"},
|
||||
}
|
||||
drMetadata := config.Meta{
|
||||
Name: "mcpbridge-destinationrule",
|
||||
Namespace: m.namespace,
|
||||
GroupVersionKind: gvk.DestinationRule,
|
||||
// Set this label so that we do not compare configs and just push.
|
||||
Labels: map[string]string{constants.AlwaysPushLabel: "true"},
|
||||
}
|
||||
for _, f := range m.serviceEntryHandlers {
|
||||
IngressLog.Debug("McpBridge triggerd serviceEntry update")
|
||||
f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, istiomodel.EventUpdate)
|
||||
f(config.Config{Meta: seMetadata}, config.Config{Meta: seMetadata}, istiomodel.EventUpdate)
|
||||
}
|
||||
for _, f := range m.destinationRuleHandlers {
|
||||
IngressLog.Debug("McpBridge triggerd destinationRule update")
|
||||
f(config.Config{Meta: drMetadata}, config.Config{Meta: drMetadata}, istiomodel.EventUpdate)
|
||||
}
|
||||
}, m.localKubeClient, m.namespace)
|
||||
}
|
||||
@@ -1489,7 +1532,7 @@ func constructBasicAuthEnvoyFilter(rules *common.BasicAuthRules, namespace strin
|
||||
}, nil
|
||||
}
|
||||
|
||||
func QueryByName(serviceEntries []*memory.ServiceEntryWrapper, serviceName string) (*memory.ServiceEntryWrapper, error) {
|
||||
func QueryByName(serviceEntries []*memory.ServiceWrapper, serviceName string) (*memory.ServiceWrapper, error) {
|
||||
IngressLog.Infof("Found http2rpc serviceEntries %s", serviceEntries)
|
||||
for _, se := range serviceEntries {
|
||||
if se.ServiceName == serviceName {
|
||||
@@ -1499,7 +1542,7 @@ func QueryByName(serviceEntries []*memory.ServiceEntryWrapper, serviceName strin
|
||||
return nil, fmt.Errorf("can't find ServiceEntry by serviceName:%v", serviceName)
|
||||
}
|
||||
|
||||
func QueryRpcServiceVersion(serviceEntry *memory.ServiceEntryWrapper, serviceName string) (string, error) {
|
||||
func QueryRpcServiceVersion(serviceEntry *memory.ServiceWrapper, serviceName string) (string, error) {
|
||||
IngressLog.Infof("Found http2rpc serviceEntry %s", serviceEntry)
|
||||
IngressLog.Infof("Found http2rpc ServiceEntry %s", serviceEntry.ServiceEntry)
|
||||
IngressLog.Infof("Found http2rpc WorkloadSelector %s", serviceEntry.ServiceEntry.WorkloadSelector)
|
||||
|
||||
@@ -69,6 +69,8 @@ type Ingress struct {
|
||||
|
||||
Auth *AuthConfig
|
||||
|
||||
Mirror *MirrorConfig
|
||||
|
||||
Destination *DestinationConfig
|
||||
|
||||
IgnoreCase *IgnoreCaseConfig
|
||||
@@ -161,6 +163,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
|
||||
localRateLimit{},
|
||||
fallback{},
|
||||
auth{},
|
||||
mirror{},
|
||||
destination{},
|
||||
ignoreCaseMatching{},
|
||||
match{},
|
||||
@@ -182,6 +185,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
|
||||
retry{},
|
||||
localRateLimit{},
|
||||
fallback{},
|
||||
mirror{},
|
||||
ignoreCaseMatching{},
|
||||
match{},
|
||||
headerControl{},
|
||||
|
||||
118
pkg/ingress/kube/annotations/mirror.go
Normal file
118
pkg/ingress/kube/annotations/mirror.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2023 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/util"
|
||||
. "github.com/alibaba/higress/pkg/ingress/log"
|
||||
wrappers "google.golang.org/protobuf/types/known/wrapperspb"
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
)
|
||||
|
||||
const (
|
||||
mirrorTargetService = "mirror-target-service"
|
||||
mirrorPercentage = "mirror-percentage"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Parser = &mirror{}
|
||||
_ RouteHandler = &mirror{}
|
||||
)
|
||||
|
||||
type MirrorConfig struct {
|
||||
util.ServiceInfo
|
||||
Percentage *wrappers.DoubleValue
|
||||
}
|
||||
|
||||
type mirror struct{}
|
||||
|
||||
func (m mirror) Parse(annotations Annotations, config *Ingress, globalContext *GlobalContext) error {
|
||||
if !needMirror(annotations) {
|
||||
return nil
|
||||
}
|
||||
|
||||
target, err := annotations.ParseStringASAP(mirrorTargetService)
|
||||
if err != nil {
|
||||
IngressLog.Errorf("Get mirror target service fail, err: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
serviceInfo, err := util.ParseServiceInfo(target, config.Namespace)
|
||||
if err != nil {
|
||||
IngressLog.Errorf("Get mirror target service fail, err: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
serviceLister, exist := globalContext.ClusterServiceList[config.ClusterId]
|
||||
if !exist {
|
||||
IngressLog.Errorf("service lister of cluster %s doesn't exist", config.ClusterId)
|
||||
return nil
|
||||
}
|
||||
|
||||
service, err := serviceLister.Services(serviceInfo.Namespace).Get(serviceInfo.Name)
|
||||
if err != nil {
|
||||
IngressLog.Errorf("Mirror service %s/%s within ingress %s/%s is not found, with err: %v",
|
||||
serviceInfo.Namespace, serviceInfo.Name, config.Namespace, config.Name, err)
|
||||
return nil
|
||||
}
|
||||
if service == nil {
|
||||
IngressLog.Errorf("service %s/%s within ingress %s/%s is empty value",
|
||||
serviceInfo.Namespace, serviceInfo.Name, config.Namespace, config.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
if serviceInfo.Port == 0 {
|
||||
// Use the first port
|
||||
serviceInfo.Port = uint32(service.Spec.Ports[0].Port)
|
||||
}
|
||||
|
||||
var percentage *wrappers.DoubleValue
|
||||
if value, err := annotations.ParseIntASAP(mirrorPercentage); err == nil {
|
||||
if value < 100 {
|
||||
percentage = &wrappers.DoubleValue{
|
||||
Value: float64(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.Mirror = &MirrorConfig{
|
||||
ServiceInfo: serviceInfo,
|
||||
Percentage: percentage,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mirror) ApplyRoute(route *networking.HTTPRoute, config *Ingress) {
|
||||
if config.Mirror == nil {
|
||||
return
|
||||
}
|
||||
|
||||
route.Mirror = &networking.Destination{
|
||||
Host: util.CreateServiceFQDN(config.Mirror.Namespace, config.Mirror.Name),
|
||||
Port: &networking.PortSelector{
|
||||
Number: config.Mirror.Port,
|
||||
},
|
||||
}
|
||||
|
||||
if config.Mirror.Percentage != nil {
|
||||
route.MirrorPercentage = &networking.Percent{
|
||||
Value: config.Mirror.Percentage.GetValue(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func needMirror(annotations Annotations) bool {
|
||||
return annotations.HasASAP(mirrorTargetService)
|
||||
}
|
||||
163
pkg/ingress/kube/annotations/mirror_test.go
Normal file
163
pkg/ingress/kube/annotations/mirror_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/util"
|
||||
"github.com/golang/protobuf/proto"
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseMirror(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input []map[string]string
|
||||
expect *MirrorConfig
|
||||
}{
|
||||
{},
|
||||
{
|
||||
input: []map[string]string{
|
||||
{buildHigressAnnotationKey(mirrorTargetService): "test/app"},
|
||||
{buildNginxAnnotationKey(mirrorTargetService): "test/app"},
|
||||
},
|
||||
expect: &MirrorConfig{
|
||||
ServiceInfo: util.ServiceInfo{
|
||||
NamespacedName: model.NamespacedName{
|
||||
Namespace: "test",
|
||||
Name: "app",
|
||||
},
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []map[string]string{
|
||||
{buildHigressAnnotationKey(mirrorTargetService): "test/app:8080"},
|
||||
{buildNginxAnnotationKey(mirrorTargetService): "test/app:8080"},
|
||||
},
|
||||
expect: &MirrorConfig{
|
||||
ServiceInfo: util.ServiceInfo{
|
||||
NamespacedName: model.NamespacedName{
|
||||
Namespace: "test",
|
||||
Name: "app",
|
||||
},
|
||||
Port: 8080,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []map[string]string{
|
||||
{buildHigressAnnotationKey(mirrorTargetService): "test/app:hi"},
|
||||
{buildNginxAnnotationKey(mirrorTargetService): "test/app:hi"},
|
||||
},
|
||||
expect: &MirrorConfig{
|
||||
ServiceInfo: util.ServiceInfo{
|
||||
NamespacedName: model.NamespacedName{
|
||||
Namespace: "test",
|
||||
Name: "app",
|
||||
},
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []map[string]string{
|
||||
{buildHigressAnnotationKey(mirrorTargetService): "test/app"},
|
||||
{buildNginxAnnotationKey(mirrorTargetService): "test/app"},
|
||||
},
|
||||
expect: &MirrorConfig{
|
||||
ServiceInfo: util.ServiceInfo{
|
||||
NamespacedName: model.NamespacedName{
|
||||
Namespace: "test",
|
||||
Name: "app",
|
||||
},
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mirror := mirror{}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
config := &Ingress{
|
||||
Meta: Meta{
|
||||
Namespace: "test",
|
||||
ClusterId: "cluster",
|
||||
},
|
||||
}
|
||||
globalContext, cancel := initGlobalContextForService()
|
||||
defer cancel()
|
||||
|
||||
for _, in := range testCase.input {
|
||||
_ = mirror.Parse(in, config, globalContext)
|
||||
if !reflect.DeepEqual(testCase.expect, config.Mirror) {
|
||||
t.Log("expect:", *testCase.expect)
|
||||
t.Log("actual:", *config.Mirror)
|
||||
t.Fatal("Should be equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMirror_ApplyRoute(t *testing.T) {
|
||||
testCases := []struct {
|
||||
config *Ingress
|
||||
input *networking.HTTPRoute
|
||||
expect *networking.HTTPRoute
|
||||
}{
|
||||
{
|
||||
config: &Ingress{},
|
||||
input: &networking.HTTPRoute{},
|
||||
expect: &networking.HTTPRoute{},
|
||||
},
|
||||
{
|
||||
config: &Ingress{
|
||||
Mirror: &MirrorConfig{
|
||||
ServiceInfo: util.ServiceInfo{
|
||||
NamespacedName: model.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test",
|
||||
},
|
||||
Port: 8080,
|
||||
},
|
||||
},
|
||||
},
|
||||
input: &networking.HTTPRoute{},
|
||||
expect: &networking.HTTPRoute{
|
||||
Mirror: &networking.Destination{
|
||||
Host: "test.default.svc.cluster.local",
|
||||
Port: &networking.PortSelector{
|
||||
Number: 8080,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mirror := mirror{}
|
||||
for _, testCase := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
mirror.ApplyRoute(testCase.input, testCase.config)
|
||||
if !proto.Equal(testCase.input, testCase.expect) {
|
||||
t.Fatal("Must be equal.")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,15 @@ type WrapperGateway struct {
|
||||
Host string
|
||||
}
|
||||
|
||||
func CreateMcpServiceKey(host string, portNumber int32) ServiceKey {
|
||||
return ServiceKey{
|
||||
Namespace: "mcp",
|
||||
Name: host,
|
||||
ServiceFQDN: host,
|
||||
Port: portNumber,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WrapperGateway) IsHTTPS() bool {
|
||||
if w.Gateway == nil || len(w.Gateway.Servers) == 0 {
|
||||
return false
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
kubecredentials "istio.io/istio/pilot/pkg/credentials/kube"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
kubecontroller "istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
|
||||
"istio.io/istio/pilot/pkg/status"
|
||||
"istio.io/istio/pkg/config"
|
||||
"istio.io/istio/pkg/config/constants"
|
||||
"istio.io/istio/pkg/config/schema/collection"
|
||||
@@ -48,6 +49,7 @@ type gatewayController struct {
|
||||
store model.ConfigStoreController
|
||||
credsController credentials.MulticlusterController
|
||||
istioController *istiogateway.Controller
|
||||
statusManager *status.Manager
|
||||
|
||||
resourceUpToDate atomic.Bool
|
||||
}
|
||||
@@ -76,9 +78,10 @@ func NewController(client kube.Client, options common.Options) common.GatewayCon
|
||||
istioController.DefaultGatewaySelector = map[string]string{options.GatewaySelectorKey: options.GatewaySelectorValue}
|
||||
}
|
||||
|
||||
var statusManager *status.Manager = nil
|
||||
if options.EnableStatus {
|
||||
// TODO: Add status sync support
|
||||
//istioController.SetStatusWrite(true,)
|
||||
statusManager = status.NewManager(store)
|
||||
istioController.SetStatusWrite(true, statusManager)
|
||||
} else {
|
||||
IngressLog.Infof("Disable status update for cluster %s", clusterId)
|
||||
}
|
||||
@@ -87,6 +90,7 @@ func NewController(client kube.Client, options common.Options) common.GatewayCon
|
||||
store: store,
|
||||
credsController: credsController,
|
||||
istioController: istioController,
|
||||
statusManager: statusManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +152,9 @@ func (g *gatewayController) Run(stop <-chan struct{}) {
|
||||
})
|
||||
go g.store.Run(stop)
|
||||
go g.istioController.Run(stop)
|
||||
if g.statusManager != nil {
|
||||
g.statusManager.Start(stop)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gatewayController) SetWatchErrorHandler(f func(r *cache.Reflector, err error)) error {
|
||||
|
||||
@@ -15,26 +15,37 @@
|
||||
package istio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
serviceRegistryKube "istio.io/istio/pilot/pkg/serviceregistry/kube"
|
||||
"istio.io/istio/pkg/cluster"
|
||||
"istio.io/istio/pkg/config/host"
|
||||
"istio.io/istio/pkg/config/schema/gvk"
|
||||
"istio.io/istio/pkg/kube"
|
||||
"istio.io/istio/pkg/util/sets"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// GatewayContext contains a minimal subset of push context functionality to be exposed to GatewayAPIControllers
|
||||
type GatewayContext struct {
|
||||
ps *model.PushContext
|
||||
// Start - Updated by Higress
|
||||
client kube.Client
|
||||
domainSuffix string
|
||||
clusterID cluster.ID
|
||||
// End - Updated by Higress
|
||||
}
|
||||
|
||||
func NewGatewayContext(ps *model.PushContext) GatewayContext {
|
||||
return GatewayContext{ps}
|
||||
// Start - Updated by Higress
|
||||
|
||||
func NewGatewayContext(ps *model.PushContext, client kube.Client, domainSuffix string, clusterID cluster.ID) GatewayContext {
|
||||
return GatewayContext{ps, client, domainSuffix, clusterID}
|
||||
}
|
||||
|
||||
// ResolveGatewayInstances attempts to resolve all instances that a gateway will be exposed on.
|
||||
@@ -59,26 +70,20 @@ func (gc GatewayContext) ResolveGatewayInstances(
|
||||
foundExternal := sets.New[string]()
|
||||
foundPending := sets.New[string]()
|
||||
warnings := []string{}
|
||||
|
||||
// Cache endpoints to reduce redundant queries
|
||||
endpointsCache := make(map[string]*corev1.Endpoints)
|
||||
|
||||
for _, g := range gwsvcs {
|
||||
svc, f := gc.ps.ServiceIndex.HostnameAndNamespace[host.Name(g)][namespace]
|
||||
if !f {
|
||||
otherNamespaces := []string{}
|
||||
for ns := range gc.ps.ServiceIndex.HostnameAndNamespace[host.Name(g)] {
|
||||
otherNamespaces = append(otherNamespaces, `"`+ns+`"`) // Wrap in quotes for output
|
||||
}
|
||||
if len(otherNamespaces) > 0 {
|
||||
sort.Strings(otherNamespaces)
|
||||
warnings = append(warnings, fmt.Sprintf("hostname %q not found in namespace %q, but it was found in namespace(s) %v",
|
||||
g, namespace, strings.Join(otherNamespaces, ", ")))
|
||||
} else {
|
||||
warnings = append(warnings, fmt.Sprintf("hostname %q not found", g))
|
||||
}
|
||||
svc := gc.GetService(g, namespace, gvk.Service.Kind)
|
||||
if svc == nil {
|
||||
warnings = append(warnings, fmt.Sprintf("hostname %q not found", g))
|
||||
continue
|
||||
}
|
||||
svcKey := svc.Key()
|
||||
|
||||
for port := range ports {
|
||||
instances := gc.ps.ServiceInstancesByPort(svc, port, nil)
|
||||
if len(instances) > 0 {
|
||||
exists := checkServicePortExists(svc, port)
|
||||
if exists {
|
||||
foundInternal.Insert(fmt.Sprintf("%s:%d", g, port))
|
||||
if svc.Attributes.ClusterExternalAddresses.Len() > 0 {
|
||||
// Fetch external IPs from all clusters
|
||||
@@ -92,22 +97,30 @@ func (gc GatewayContext) ResolveGatewayInstances(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instancesByPort := gc.ps.ServiceInstances(svcKey)
|
||||
if instancesEmpty(instancesByPort) {
|
||||
endpoints, ok := endpointsCache[g]
|
||||
if !ok {
|
||||
endpoints = gc.GetEndpoints(g, namespace)
|
||||
endpointsCache[g] = endpoints
|
||||
}
|
||||
|
||||
if endpoints == nil {
|
||||
warnings = append(warnings, fmt.Sprintf("no instances found for hostname %q", g))
|
||||
} else {
|
||||
hintPort := sets.New[string]()
|
||||
for _, instances := range instancesByPort {
|
||||
for _, i := range instances {
|
||||
if i.Endpoint.EndpointPort == uint32(port) {
|
||||
hintPort.Insert(strconv.Itoa(i.ServicePort.Port))
|
||||
hintWorkloadPort := false
|
||||
for _, subset := range endpoints.Subsets {
|
||||
for _, subSetPort := range subset.Ports {
|
||||
if subSetPort.Port == int32(port) {
|
||||
hintWorkloadPort = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hintWorkloadPort {
|
||||
break
|
||||
}
|
||||
}
|
||||
if hintPort.Len() > 0 {
|
||||
if hintWorkloadPort {
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
"port %d not found for hostname %q (hint: the service port should be specified, not the workload port. Did you mean one of these ports: %v?)",
|
||||
port, g, sets.SortedList(hintPort)))
|
||||
"port %d not found for hostname %q (hint: the service port should be specified, not the workload port", port, g))
|
||||
} else {
|
||||
warnings = append(warnings, fmt.Sprintf("port %d not found for hostname %q", port, g))
|
||||
}
|
||||
@@ -119,15 +132,60 @@ func (gc GatewayContext) ResolveGatewayInstances(
|
||||
return sets.SortedList(foundInternal), sets.SortedList(foundExternal), sets.SortedList(foundPending), warnings
|
||||
}
|
||||
|
||||
func (gc GatewayContext) GetService(hostname, namespace string) *model.Service {
|
||||
return gc.ps.ServiceIndex.HostnameAndNamespace[host.Name(hostname)][namespace]
|
||||
func (gc GatewayContext) GetService(hostname, namespace, kind string) *model.Service {
|
||||
// Currently only supports type Kubernetes Service
|
||||
if kind != gvk.Service.Kind {
|
||||
log.Warnf("Unsupported kind: expected 'Service', but got '%s'", kind)
|
||||
return nil
|
||||
}
|
||||
serviceName := extractServiceName(hostname)
|
||||
|
||||
svc, err := gc.client.Kube().CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
log.Errorf("failed to get service (serviceName: %s, namespace: %s): %v", serviceName, namespace, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return serviceRegistryKube.ConvertService(*svc, gc.domainSuffix, gc.clusterID)
|
||||
}
|
||||
|
||||
func instancesEmpty(m map[int][]*model.ServiceInstance) bool {
|
||||
for _, instances := range m {
|
||||
if len(instances) > 0 {
|
||||
return false
|
||||
func (gc GatewayContext) GetEndpoints(hostname, namespace string) *corev1.Endpoints {
|
||||
serviceName := extractServiceName(hostname)
|
||||
|
||||
endpoints, err := gc.client.Kube().CoreV1().Endpoints(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
log.Errorf("failed to get endpoints (serviceName: %s, namespace: %s): %v", serviceName, namespace, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func checkServicePortExists(svc *model.Service, port int) bool {
|
||||
if svc == nil {
|
||||
return false
|
||||
}
|
||||
for _, svcPort := range svc.Ports {
|
||||
if port == svcPort.Port {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
func extractServiceName(hostName string) string {
|
||||
parts := strings.Split(hostName, ".")
|
||||
if len(parts) >= 4 {
|
||||
return parts[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// End - Updated by Higress
|
||||
|
||||
@@ -201,7 +201,9 @@ func (c *Controller) Reconcile(ps *model.PushContext) error {
|
||||
ReferenceGrant: referenceGrant,
|
||||
DefaultGatewaySelector: c.DefaultGatewaySelector,
|
||||
Domain: c.domain,
|
||||
Context: NewGatewayContext(ps),
|
||||
// Start - Updated by Higress
|
||||
Context: NewGatewayContext(ps, c.client, c.domain, c.cluster),
|
||||
// End - Updated by Higress
|
||||
}
|
||||
|
||||
if !input.hasResources() {
|
||||
|
||||
@@ -1168,7 +1168,7 @@ func buildDestination(ctx configContext, to k8s.BackendRef, ns string, enforceRe
|
||||
return nil, &ConfigError{Reason: InvalidDestination, Message: "serviceName invalid; the name of the Service must be used, not the hostname."}
|
||||
}
|
||||
hostname := fmt.Sprintf("%s.%s.svc.%s", to.Name, namespace, ctx.Domain)
|
||||
if ctx.Context.GetService(hostname, namespace) == nil {
|
||||
if ctx.Context.GetService(hostname, namespace, gvk.Service.Kind) == nil {
|
||||
invalidBackendErr = &ConfigError{Reason: InvalidDestinationNotFound, Message: fmt.Sprintf("backend(%s) not found", hostname)}
|
||||
}
|
||||
return &istio.Destination{
|
||||
@@ -1192,7 +1192,7 @@ func buildDestination(ctx configContext, to k8s.BackendRef, ns string, enforceRe
|
||||
if strings.Contains(string(to.Name), ".") {
|
||||
return nil, &ConfigError{Reason: InvalidDestination, Message: "serviceName invalid; the name of the Service must be used, not the hostname."}
|
||||
}
|
||||
if ctx.Context.GetService(hostname, namespace) == nil {
|
||||
if ctx.Context.GetService(hostname, namespace, "ServiceImport") == nil {
|
||||
invalidBackendErr = &ConfigError{Reason: InvalidDestinationNotFound, Message: fmt.Sprintf("backend(%s) not found", hostname)}
|
||||
}
|
||||
return &istio.Destination{
|
||||
@@ -1210,7 +1210,7 @@ func buildDestination(ctx configContext, to k8s.BackendRef, ns string, enforceRe
|
||||
return nil, &ConfigError{Reason: InvalidDestination, Message: "namespace may not be set with Hostname type"}
|
||||
}
|
||||
hostname := string(to.Name)
|
||||
if ctx.Context.GetService(hostname, namespace) == nil {
|
||||
if ctx.Context.GetService(hostname, namespace, "Hostname") == nil {
|
||||
invalidBackendErr = &ConfigError{Reason: InvalidDestinationNotFound, Message: fmt.Sprintf("backend(%s) not found", hostname)}
|
||||
}
|
||||
return &istio.Destination{
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package istio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"istio.io/istio/pilot/pkg/config/kube/crd"
|
||||
credentials "istio.io/istio/pilot/pkg/credentials/kube"
|
||||
"istio.io/istio/pilot/pkg/features"
|
||||
@@ -47,7 +49,8 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var ports = []*model.Port{
|
||||
// Start - Updated by Higress
|
||||
var ports = []corev1.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
@@ -64,232 +67,291 @@ var defaultGatewaySelector = map[string]string{
|
||||
"higress": "higress-system-higress-gateway",
|
||||
}
|
||||
|
||||
var services = []*model.Service{
|
||||
var services = []corev1.Service{
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "higress-gateway",
|
||||
Namespace: "higress-system",
|
||||
ClusterExternalAddresses: &model.AddressMap{
|
||||
Addresses: map[cluster.ID][]string{
|
||||
"Kubernetes": {"1.2.3.4"},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
ExternalIPs: []string{"1.2.3.4"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example.com",
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-apple",
|
||||
Namespace: "apple",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-banana",
|
||||
Namespace: "banana",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-second",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-wildcard",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-svc",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-other",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "echo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin",
|
||||
Namespace: "cert",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-svc",
|
||||
Namespace: "service",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "google.com",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc2",
|
||||
Namespace: "allowed-1",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc2",
|
||||
Namespace: "allowed-2",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc1",
|
||||
Namespace: "allowed-1",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc3",
|
||||
Namespace: "allowed-2",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "svc4",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin",
|
||||
Namespace: "group-namespace1",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin",
|
||||
Namespace: "group-namespace2",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-zero",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin",
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-mirror",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-foo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-alt",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "higress-controller",
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "echo",
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "httpbin-bad",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var endpoints = []corev1.Endpoints{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "higress-gateway",
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Subsets: []corev1.EndpointSubset{
|
||||
{
|
||||
Ports: []corev1.EndpointPort{
|
||||
{
|
||||
Port: 8080,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "higress-gateway.higress-system.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "example.com",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "apple",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-apple.apple.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "banana",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-banana.banana.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-second.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-wildcard.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "foo-svc.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-other.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "example.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "echo.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "echo.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "cert",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin.cert.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "service",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "my-svc.service.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "google.com",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "allowed-1",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "svc2.allowed-1.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "allowed-2",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "svc2.allowed-2.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "allowed-1",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "svc1.allowed-1.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "allowed-2",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "svc3.allowed-2.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "svc4.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "group-namespace1",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin.group-namespace1.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "group-namespace2",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin.group-namespace2.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-zero.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin.higress-system.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-mirror.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-foo.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-alt.default.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "higress-controller.higress-system.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "higress-controller.higress-system.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "higress-system",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "echo.higress-system.svc.domain.suffix",
|
||||
},
|
||||
{
|
||||
Attributes: model.ServiceAttributes{
|
||||
Namespace: "default",
|
||||
},
|
||||
Ports: ports,
|
||||
Hostname: "httpbin-bad.default.svc.domain.suffix",
|
||||
},
|
||||
}
|
||||
|
||||
// End - Updated by Higress
|
||||
|
||||
var (
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.25.4/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_tls_test.go#L31
|
||||
rsaCertPEM = `-----BEGIN CERTIFICATE-----
|
||||
@@ -364,6 +426,21 @@ func init() {
|
||||
|
||||
func TestConvertResources(t *testing.T) {
|
||||
validator := crdvalidation.NewIstioValidator(t)
|
||||
|
||||
// Start - Updated by Higress
|
||||
client := kube.NewFakeClient()
|
||||
for _, svc := range services {
|
||||
if _, err := client.Kube().CoreV1().Services(svc.Namespace).Create(context.TODO(), &svc, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for _, endpoint := range endpoints {
|
||||
if _, err := client.Kube().CoreV1().Endpoints(endpoint.Namespace).Create(context.TODO(), &endpoint, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// End - Updated by Higress
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
}{
|
||||
@@ -374,38 +451,23 @@ func TestConvertResources(t *testing.T) {
|
||||
{"weighted"},
|
||||
{"zero"},
|
||||
{"invalid"},
|
||||
{"multi-gateway"},
|
||||
// 目前仅支持 type 为 Hostname 和 ServiceImport
|
||||
//{"multi-gateway"},
|
||||
{"delegated"},
|
||||
{"route-binding"},
|
||||
{"reference-policy-tls"},
|
||||
{"reference-policy-service"},
|
||||
{"serviceentry"},
|
||||
//{"serviceentry"},
|
||||
{"alias"},
|
||||
{"mcs"},
|
||||
//{"mcs"},
|
||||
{"route-precedence"},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
input := readConfig(t, fmt.Sprintf("testdata/%s.yaml", tt.name), validator)
|
||||
// Setup a few preconfigured services
|
||||
instances := []*model.ServiceInstance{}
|
||||
for _, svc := range services {
|
||||
instances = append(instances, &model.ServiceInstance{
|
||||
Service: svc,
|
||||
ServicePort: ports[0],
|
||||
Endpoint: &model.IstioEndpoint{EndpointPort: 8080},
|
||||
}, &model.ServiceInstance{
|
||||
Service: svc,
|
||||
ServicePort: ports[1],
|
||||
Endpoint: &model.IstioEndpoint{},
|
||||
})
|
||||
}
|
||||
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
|
||||
Services: services,
|
||||
Instances: instances,
|
||||
})
|
||||
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
|
||||
kr := splitInput(t, input)
|
||||
kr.Context = NewGatewayContext(cg.PushContext())
|
||||
kr.Context = NewGatewayContext(cg.PushContext(), client, "domain.suffix", "")
|
||||
output := convertResources(kr)
|
||||
output.AllowedReferences = AllowedReferences{} // Not tested here
|
||||
output.ReferencedNamespaceKeys = nil // Not tested here
|
||||
@@ -427,20 +489,20 @@ func TestConvertResources(t *testing.T) {
|
||||
|
||||
assert.Equal(t, golden, output)
|
||||
|
||||
//outputStatus := getStatus(t, kr.GatewayClass, kr.Gateway, kr.HTTPRoute, kr.TLSRoute, kr.TCPRoute)
|
||||
//goldenStatusFile := fmt.Sprintf("testdata/%s.status.yaml.golden", tt.name)
|
||||
//if util.Refresh() {
|
||||
// if err := os.WriteFile(goldenStatusFile, outputStatus, 0o644); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//}
|
||||
//goldenStatus, err := os.ReadFile(goldenStatusFile)
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//if diff := cmp.Diff(string(goldenStatus), string(outputStatus)); diff != "" {
|
||||
// t.Fatalf("Diff:\n%s", diff)
|
||||
//}
|
||||
outputStatus := getStatus(t, kr.GatewayClass, kr.Gateway, kr.HTTPRoute, kr.TLSRoute, kr.TCPRoute)
|
||||
goldenStatusFile := fmt.Sprintf("testdata/%s.status.yaml.golden", tt.name)
|
||||
if util.Refresh() {
|
||||
if err := os.WriteFile(goldenStatusFile, outputStatus, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
goldenStatus, err := os.ReadFile(goldenStatusFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(string(goldenStatus), string(outputStatus)); diff != "" {
|
||||
t.Fatalf("Diff:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -593,7 +655,7 @@ spec:
|
||||
input := readConfigString(t, tt.config, validator)
|
||||
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
|
||||
kr := splitInput(t, input)
|
||||
kr.Context = NewGatewayContext(cg.PushContext())
|
||||
kr.Context = NewGatewayContext(cg.PushContext(), nil, "", "")
|
||||
output := convertResources(kr)
|
||||
c := &Controller{
|
||||
state: output,
|
||||
@@ -814,7 +876,7 @@ func BenchmarkBuildHTTPVirtualServices(b *testing.B) {
|
||||
validator := crdvalidation.NewIstioValidator(b)
|
||||
input := readConfig(b, "testdata/benchmark-httproute.yaml", validator)
|
||||
kr := splitInput(b, input)
|
||||
kr.Context = NewGatewayContext(cg.PushContext())
|
||||
kr.Context = NewGatewayContext(cg.PushContext(), nil, "", "")
|
||||
ctx := configContext{
|
||||
GatewayResources: kr,
|
||||
AllowedReferences: convertReferencePolicies(kr),
|
||||
|
||||
@@ -128,8 +128,7 @@ status:
|
||||
- lastTransitionTime: fake
|
||||
message: 'Failed to assign to any requested addresses: port 8080 not found for
|
||||
hostname "higress-gateway.higress-system.svc.domain.suffix" (hint: the service
|
||||
port should be specified, not the workload port. Did you mean one of these ports:
|
||||
[80]?)'
|
||||
port should be specified, not the workload port'
|
||||
reason: Invalid
|
||||
status: "False"
|
||||
type: Programmed
|
||||
@@ -163,26 +162,6 @@ status:
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: invalid-gateway-address
|
||||
namespace: invalid-gateway-address
|
||||
spec: null
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: fake
|
||||
message: only Hostname is supported, ignoring [1.2.3.4]
|
||||
reason: UnsupportedAddress
|
||||
status: "False"
|
||||
type: Accepted
|
||||
- lastTransitionTime: fake
|
||||
message: Failed to assign to any requested addresses
|
||||
reason: UnsupportedAddress
|
||||
status: "False"
|
||||
type: Programmed
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: invalid-cert-kind
|
||||
@@ -477,4 +456,29 @@ status:
|
||||
namespace: higress-system
|
||||
sectionName: fake
|
||||
---
|
||||
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: no-backend
|
||||
namespace: default
|
||||
spec: null
|
||||
status:
|
||||
parents:
|
||||
- conditions:
|
||||
- lastTransitionTime: fake
|
||||
message: Route was valid
|
||||
reason: Accepted
|
||||
status: "True"
|
||||
type: Accepted
|
||||
- lastTransitionTime: fake
|
||||
message: All references resolved
|
||||
reason: ResolvedRefs
|
||||
status: "True"
|
||||
type: ResolvedRefs
|
||||
controllerName: higress.io/gateway-controller
|
||||
parentRef:
|
||||
group: ""
|
||||
kind: Service
|
||||
name: httpbin
|
||||
---
|
||||
|
||||
@@ -55,22 +55,23 @@ spec:
|
||||
hostname: "*.example"
|
||||
port: 8080 # Test service has port 80 with targetPort 8080
|
||||
protocol: HTTP
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: invalid-gateway-address
|
||||
namespace: invalid-gateway-address
|
||||
spec:
|
||||
gatewayClassName: higress
|
||||
addresses:
|
||||
- value: 1.2.3.4
|
||||
type: istio.io/FakeType
|
||||
listeners:
|
||||
- name: default
|
||||
hostname: "*.domain.example"
|
||||
port: 80
|
||||
protocol: HTTP
|
||||
#---
|
||||
# Higress 仅支持 addresses type 为 Hostname
|
||||
#apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||
#kind: Gateway
|
||||
#metadata:
|
||||
# name: invalid-gateway-address
|
||||
# namespace: invalid-gateway-address
|
||||
#spec:
|
||||
# gatewayClassName: higress
|
||||
# addresses:
|
||||
# - value: 1.2.3.4
|
||||
# type: istio.io/FakeType
|
||||
# listeners:
|
||||
# - name: default
|
||||
# hostname: "*.domain.example"
|
||||
# port: 80
|
||||
# protocol: HTTP
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||
kind: Gateway
|
||||
|
||||
@@ -53,25 +53,6 @@ spec:
|
||||
protocol: HTTP
|
||||
---
|
||||
apiVersion: networking.istio.io/v1alpha3
|
||||
kind: Gateway
|
||||
metadata:
|
||||
annotations:
|
||||
internal.istio.io/parents: Gateway/invalid-gateway-address/default.invalid-gateway-address
|
||||
creationTimestamp: null
|
||||
name: invalid-gateway-address-istio-autogenerated-k8s-gateway-default
|
||||
namespace: invalid-gateway-address
|
||||
spec:
|
||||
selector:
|
||||
higress: higress-system-higress-gateway
|
||||
servers:
|
||||
- hosts:
|
||||
- invalid-gateway-address/*.domain.example
|
||||
port:
|
||||
name: default
|
||||
number: 80
|
||||
protocol: HTTP
|
||||
---
|
||||
apiVersion: networking.istio.io/v1alpha3
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
annotations:
|
||||
|
||||
@@ -920,12 +920,7 @@ func (c *controller) storeBackendTrafficPolicy(wrapper *common.WrapperConfig, ba
|
||||
if common.ValidateBackendResource(backend.Resource) && wrapper.AnnotationsConfig.Destination != nil {
|
||||
for _, dest := range wrapper.AnnotationsConfig.Destination.McpDestination {
|
||||
portNumber := dest.Destination.GetPort().GetNumber()
|
||||
serviceKey := common.ServiceKey{
|
||||
Namespace: "mcp",
|
||||
Name: dest.Destination.Host,
|
||||
Port: int32(portNumber),
|
||||
ServiceFQDN: dest.Destination.Host,
|
||||
}
|
||||
serviceKey := common.CreateMcpServiceKey(dest.Destination.Host, int32(portNumber))
|
||||
if _, exist := store[serviceKey]; !exist {
|
||||
if serviceKey.Port != 0 {
|
||||
store[serviceKey] = &common.WrapperTrafficPolicy{
|
||||
|
||||
@@ -900,12 +900,7 @@ func (c *controller) storeBackendTrafficPolicy(wrapper *common.WrapperConfig, ba
|
||||
if common.ValidateBackendResource(backend.Resource) && wrapper.AnnotationsConfig.Destination != nil {
|
||||
for _, dest := range wrapper.AnnotationsConfig.Destination.McpDestination {
|
||||
portNumber := dest.Destination.GetPort().GetNumber()
|
||||
serviceKey := common.ServiceKey{
|
||||
Namespace: "mcp",
|
||||
Name: dest.Destination.Host,
|
||||
Port: int32(portNumber),
|
||||
ServiceFQDN: dest.Destination.Host,
|
||||
}
|
||||
serviceKey := common.CreateMcpServiceKey(dest.Destination.Host, int32(portNumber))
|
||||
if _, exist := store[serviceKey]; !exist {
|
||||
if serviceKey.Port != 0 {
|
||||
store[serviceKey] = &common.WrapperTrafficPolicy{
|
||||
|
||||
@@ -20,8 +20,10 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
@@ -113,3 +115,44 @@ func BuildPatchStruct(config string) *_struct.Struct {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
type ServiceInfo struct {
|
||||
model.NamespacedName
|
||||
Port uint32
|
||||
}
|
||||
|
||||
// convertToPort converts a port string to a uint32.
|
||||
func convertToPort(v string) (uint32, error) {
|
||||
p, err := strconv.ParseUint(v, 10, 32)
|
||||
if err != nil || p > 65535 {
|
||||
return 0, fmt.Errorf("invalid port %s: %v", v, err)
|
||||
}
|
||||
return uint32(p), nil
|
||||
}
|
||||
|
||||
func ParseServiceInfo(service string, ingressNamespace string) (ServiceInfo, error) {
|
||||
parts := strings.Split(service, ":")
|
||||
namespacedName := SplitNamespacedName(parts[0])
|
||||
|
||||
if namespacedName.Name == "" {
|
||||
return ServiceInfo{}, errors.New("service name can not be empty")
|
||||
}
|
||||
|
||||
if namespacedName.Namespace == "" {
|
||||
namespacedName.Namespace = ingressNamespace
|
||||
}
|
||||
|
||||
var port uint32
|
||||
if len(parts) == 2 {
|
||||
// If port parse fail, we ignore port and pick the first one.
|
||||
port, _ = convertToPort(parts[1])
|
||||
}
|
||||
|
||||
return ServiceInfo{
|
||||
NamespacedName: model.NamespacedName{
|
||||
Name: namespacedName.Name,
|
||||
Namespace: namespacedName.Namespace,
|
||||
},
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func (c ServiceEntryGenerator) Generate(proxy *model.Proxy, w *model.WatchedReso
|
||||
return serviceEntries[i].CreationTimestamp.Before(serviceEntries[j].CreationTimestamp)
|
||||
})
|
||||
}
|
||||
return generate(proxy, serviceEntries, w, updates, false, false)
|
||||
return generate(proxy, serviceEntries, w, updates, c.GeneratorOptions.KeepConfigLabels, c.GeneratorOptions.KeepConfigAnnotations)
|
||||
}
|
||||
|
||||
func (c ServiceEntryGenerator) GenerateDeltas(proxy *model.Proxy, updates *model.PushRequest,
|
||||
@@ -82,7 +82,7 @@ type VirtualServiceGenerator struct {
|
||||
func (c VirtualServiceGenerator) Generate(proxy *model.Proxy, w *model.WatchedResource,
|
||||
updates *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
|
||||
virtualServices := c.Environment.List(gvk.VirtualService, model.NamespaceAll)
|
||||
return generate(proxy, virtualServices, w, updates, false, false)
|
||||
return generate(proxy, virtualServices, w, updates, c.GeneratorOptions.KeepConfigLabels, c.GeneratorOptions.KeepConfigAnnotations)
|
||||
}
|
||||
|
||||
func (c VirtualServiceGenerator) GenerateDeltas(proxy *model.Proxy, updates *model.PushRequest,
|
||||
@@ -100,7 +100,7 @@ type DestinationRuleGenerator struct {
|
||||
func (c DestinationRuleGenerator) Generate(proxy *model.Proxy, w *model.WatchedResource,
|
||||
updates *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
|
||||
rules := c.Environment.List(gvk.DestinationRule, model.NamespaceAll)
|
||||
return generate(proxy, rules, w, updates, false, false)
|
||||
return generate(proxy, rules, w, updates, c.GeneratorOptions.KeepConfigLabels, c.GeneratorOptions.KeepConfigAnnotations)
|
||||
}
|
||||
|
||||
func (c DestinationRuleGenerator) GenerateDeltas(proxy *model.Proxy, updates *model.PushRequest,
|
||||
@@ -118,7 +118,7 @@ type EnvoyFilterGenerator struct {
|
||||
func (c EnvoyFilterGenerator) Generate(proxy *model.Proxy, w *model.WatchedResource,
|
||||
updates *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
|
||||
filters := c.Environment.List(gvk.EnvoyFilter, model.NamespaceAll)
|
||||
return generate(proxy, filters, w, updates, false, false)
|
||||
return generate(proxy, filters, w, updates, c.GeneratorOptions.KeepConfigLabels, c.GeneratorOptions.KeepConfigAnnotations)
|
||||
}
|
||||
|
||||
func (c EnvoyFilterGenerator) GenerateDeltas(proxy *model.Proxy, updates *model.PushRequest,
|
||||
@@ -154,7 +154,7 @@ type WasmPluginGenerator struct {
|
||||
func (c WasmPluginGenerator) Generate(proxy *model.Proxy, w *model.WatchedResource,
|
||||
updates *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
|
||||
wasmPlugins := c.Environment.List(gvk.WasmPlugin, model.NamespaceAll)
|
||||
return generate(proxy, wasmPlugins, w, updates, false, false)
|
||||
return generate(proxy, wasmPlugins, w, updates, c.GeneratorOptions.KeepConfigLabels, c.GeneratorOptions.KeepConfigAnnotations)
|
||||
}
|
||||
|
||||
func (c WasmPluginGenerator) GenerateDeltas(proxy *model.Proxy, push *model.PushContext, updates *model.PushRequest,
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
<p>
|
||||
<a href="README_EN.md">English</a> | 中文
|
||||
</p>
|
||||
---
|
||||
title: Basic 认证
|
||||
keywords: [higress,basic auth]
|
||||
description: Basic 认证插件配置参考
|
||||
---
|
||||
|
||||
# 功能说明
|
||||
## 功能说明
|
||||
`basic-auth`插件实现了基于 HTTP Basic Auth 标准进行认证鉴权的功能
|
||||
|
||||
# 配置字段
|
||||
## 运行属性
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | -------- | ------ | ---------------------------------------------------- |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
|
||||
插件执行阶段:`认证阶段`
|
||||
插件执行优先级:`320`
|
||||
|
||||
## 配置字段
|
||||
|
||||
**注意:**
|
||||
|
||||
- 在一个规则里,鉴权配置和认证配置不可同时存在
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
|
||||
### 认证配置
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | -------- | ------ | ---------------------------------------------------- |
|
||||
| `global_auth` | bool | 选填(**仅实例级别配置**) | - | 只能在实例级别配置,若配置为true,则全局生效认证机制; 若配置为false,则只对做了配置的域名和路由生效认证机制,若不配置则仅当没有域名和路由配置时全局生效(兼容老用户使用习惯)。 |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
|
||||
`consumers`中每一项的配置字段说明如下:
|
||||
|
||||
@@ -19,51 +33,49 @@
|
||||
| `credential` | string | 必填 | - | 配置该consumer的访问凭证 |
|
||||
| `name` | string | 必填 | - | 配置该consumer的名称 |
|
||||
|
||||
`_rules_` 中每一项的配置字段说明如下:
|
||||
### 鉴权配置(非必需)
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
|
||||
| `_match_domain_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的域名 |
|
||||
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求,配置允许访问的consumer名称 |
|
||||
|
||||
**注意:**
|
||||
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
## 配置示例
|
||||
|
||||
# 配置示例
|
||||
|
||||
## 对特定路由或域名开启认证和鉴权
|
||||
### 全局配置认证和路由粒度进行鉴权
|
||||
|
||||
以下配置将对网关特定路由或域名开启 Basic Auth 认证和鉴权,注意凭证信息中的用户名和密码之间使用":"分隔,`credential`字段不能重复
|
||||
|
||||
|
||||
在实例级别做如下插件配置:
|
||||
|
||||
```yaml
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
consumers:
|
||||
- credential: 'admin:123456'
|
||||
name: consumer1
|
||||
- credential: 'guest:abc'
|
||||
name: consumer2
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer1
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer2
|
||||
global_auth: false
|
||||
```
|
||||
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name`为`consumer1`的调用者访问,其他调用者不允许访问;
|
||||
对 route-a 和 route-b 这两个路由做如下配置:
|
||||
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name`为`consumer2`的调用者访问,其他调用者不允许访问。
|
||||
```yaml
|
||||
allow:
|
||||
- consumer1
|
||||
```
|
||||
|
||||
### 根据该配置,下列请求可以允许访问:
|
||||
对 *.example.com 和 test.com 在这两个域名做如下配置:
|
||||
|
||||
```yaml
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
若是在控制台进行配置,此例指定的 `route-a` 和 `route-b` 即在控制台创建路由时填写的路由名称,当匹配到这两个路由时,将允许`name`为`consumer1`的调用者访问,其他调用者不允许访问;
|
||||
|
||||
此例指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name`为`consumer2`的调用者访问,其他调用者不允许访问。
|
||||
|
||||
根据该配置,下列请求可以允许访问:
|
||||
|
||||
**请求指定用户名密码**
|
||||
|
||||
@@ -77,7 +89,7 @@ curl -H 'Authorization: Basic YWRtaW46MTIzNDU2' http://xxx.hello.com/test
|
||||
|
||||
认证鉴权通过后,请求的header中会被添加一个`X-Mse-Consumer`字段,在此例中其值为`consumer1`,用以标识调用方的名称
|
||||
|
||||
### 下列请求将拒绝访问:
|
||||
下列请求将拒绝访问:
|
||||
|
||||
**请求未提供用户名密码,返回401**
|
||||
```bash
|
||||
@@ -93,22 +105,10 @@ curl -u admin:abc http://xxx.hello.com/test
|
||||
curl -u guest:abc http://xxx.hello.com/test
|
||||
```
|
||||
|
||||
## 网关实例级别开启
|
||||
## 相关错误码
|
||||
|
||||
以下配置未指定`_rules_`字段,因此将对网关实例级别开启 Basic Auth 认证
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- credential: 'admin:123456'
|
||||
name: consumer1
|
||||
- credential: 'guest:abc'
|
||||
name: consumer2
|
||||
```
|
||||
|
||||
# 相关错误码
|
||||
|
||||
| HTTP 状态码 | 出错信息 | 原因说明 |
|
||||
| ----------- | ------------------------------------------------------------------------------ | ---------------------- |
|
||||
| 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 | 请求的调用方无访问权限 |
|
||||
| 401 | Request denied by Basic Auth check. Invalid username and/or password. | 请求凭证无效 |
|
||||
| 403 | Request denied by Basic Auth check. Unauthorized consumer. | 请求的调用方无访问权限 |
|
||||
|
||||
@@ -1,117 +1,99 @@
|
||||
<p>
|
||||
English | <a href="README.md">中文</a>
|
||||
</p>
|
||||
---
|
||||
title: Basic Authentication
|
||||
keywords: [higress,basic auth]
|
||||
description: Basic authentication plugin configuration reference
|
||||
---
|
||||
## Function Description
|
||||
The `basic-auth` plugin implements authentication and authorization based on the HTTP Basic Auth standard.
|
||||
|
||||
# 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 |
|
||||
## Operation Attributes
|
||||
Plugin execution stage: `Authentication Phase`
|
||||
Plugin execution priority: `320`
|
||||
|
||||
## Configuration Fields
|
||||
**Note:**
|
||||
- In one rule, authentication configurations and authorization configurations cannot coexist.
|
||||
- For requests that pass authentication, the request header will include an `X-Mse-Consumer` field to identify the caller's name.
|
||||
|
||||
- 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.
|
||||
### Authentication Configuration
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ------------- | ---------------- | ------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `global_auth` | bool | Optional (**instance-level only**) | - | Can only be configured at the instance level. If set to true, the authentication mechanism will take effect globally; if set to false, it will only take effect for the configured domains and routes. If not configured, it will only take effect globally when there are no domain and route configurations (compatible with old user habits). |
|
||||
| `consumers` | array of object | Required | - | Configures the service callers for request authentication. |
|
||||
|
||||
# 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.
|
||||
Each configuration field in `consumers` is described as follows:
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ------------ | --------- | ------------ | ------------- | ------------------------------- |
|
||||
| `credential` | string | Required | - | Configures the access credentials for this consumer. |
|
||||
| `name` | string | Required | - | Configures the name of this consumer. |
|
||||
|
||||
### Authorization Configuration (Optional)
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ---------------- | ---------------- | ---------------------------------------------------- | -------------- | -------------------------------------------------------- |
|
||||
| `allow` | array of string | Required | - | Configures the consumer names allowed to access for matching requests. |
|
||||
|
||||
## Configuration Example
|
||||
### Global Authentication and Route Granularity Authorization
|
||||
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 ":", and the `credential` field cannot be duplicated.
|
||||
|
||||
Make the following plugin configuration at the instance level:
|
||||
```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
|
||||
global_auth: false
|
||||
```
|
||||
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.
|
||||
For routes `route-a` and `route-b`, configure as follows:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer1
|
||||
```
|
||||
|
||||
For the domains `*.example.com` and `test.com`, configure as follows:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
### According to this configuration, the following requests are allowed:
|
||||
If configured in the console, the specified `route-a` and `route-b` refer to the route names filled in when creating the routes in the console. When matching these two routes, callers with the name `consumer1` will be allowed access, while other callers will not.
|
||||
|
||||
**Requests with specified username and password**
|
||||
The specified `*.example.com` and `test.com` are used to match the request domain. When a match is found, callers with the name `consumer2` will be allowed access, while other callers will not.
|
||||
|
||||
Based on this configuration, the following requests may be allowed access:
|
||||
**Request with specified username and password**
|
||||
```bash
|
||||
# Assuming the following request will match with route-a
|
||||
# Use -u option of curl to specify the credentials
|
||||
# Assuming the following request matches the route-a route
|
||||
# Using curl's -u parameter to specify
|
||||
curl -u admin:123456 http://xxx.hello.com/test
|
||||
# Or specify the Authorization request header directly with the credentials in base64 encoding
|
||||
# Or directly specify the Authorization request header with the username and password encoded in base64
|
||||
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.
|
||||
After successful authentication, the request header will have an added `X-Mse-Consumer` field, which in this case is `consumer1` to identify the caller's name.
|
||||
|
||||
### The following requests will be denied:
|
||||
|
||||
**Requests without providing username and password, returning 401**
|
||||
The following requests will be denied access:
|
||||
**Request without username and password, returns 401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test
|
||||
```
|
||||
**Requests with incorrect username or password, returning 401**
|
||||
|
||||
**Request with incorrect username and password, returns 401**
|
||||
```bash
|
||||
curl -u admin:abc http://xxx.hello.com/test
|
||||
```
|
||||
**Requests matched with a caller who has no access permission, returning 403**
|
||||
|
||||
**Caller matched by username and password has no access, returns 403**
|
||||
```bash
|
||||
# consumer2 is not in the allow list of route-a
|
||||
# consumer2 is not in the allow list for 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 |
|
||||
## Related Error Codes
|
||||
| HTTP Status Code | Error Message | Reason Description |
|
||||
| ---------------- | ------------------------------------------------------------------------------------- | -------------------------------- |
|
||||
| 401 | Request denied by Basic Auth check. No Basic Authentication information found. | Request did not provide credentials. |
|
||||
| 401 | Request denied by Basic Auth check. Invalid username and/or password. | Request credentials are invalid. |
|
||||
| 403 | Request denied by Basic Auth check. Unauthorized consumer. | The caller making the request does not have access. |
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
<p>
|
||||
<a href="README_EN.md"> English </a> | 中文
|
||||
</p>
|
||||
---
|
||||
title: Bot 拦截
|
||||
keywords: [higress,bot detect]
|
||||
description: Bot 拦截插件配置参考
|
||||
---
|
||||
|
||||
|
||||
## 功能说明
|
||||
|
||||
# 功能说明
|
||||
`bot-detect`插件可以用于识别并阻止互联网爬虫对站点资源的爬取
|
||||
|
||||
# 配置字段
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`授权阶段`
|
||||
插件执行优先级:`310`
|
||||
|
||||
|
||||
## 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
@@ -33,9 +43,9 @@
|
||||
(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\/\$BotVersion|123metaspider-Bot|1470\.net crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\b\w{0,30}favicon\w{0,30}\b|\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|Tiny Tiny RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s|) Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+)|)|)|)
|
||||
```
|
||||
|
||||
# 配置示例
|
||||
## 配置示例
|
||||
|
||||
## 放行原本命中爬虫规则的请求
|
||||
### 放行原本命中爬虫规则的请求
|
||||
```yaml
|
||||
allow:
|
||||
- ".*Go-http-client.*"
|
||||
@@ -44,7 +54,7 @@ allow:
|
||||
若不作该配置,默认的 Golang 网络库请求会被视做爬虫,被禁止访问
|
||||
|
||||
|
||||
## 增加爬虫判断
|
||||
### 增加爬虫判断
|
||||
```yaml
|
||||
deny:
|
||||
- "spd-tools.*"
|
||||
@@ -56,24 +66,3 @@ deny:
|
||||
curl http://example.com -H 'User-Agent: spd-tools/1.1'
|
||||
curl http://exmaple.com -H 'User-Agent: spd-tools'
|
||||
```
|
||||
|
||||
## 对特定路由或域名开启
|
||||
```yaml
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- ".*Go-http-client.*"
|
||||
```
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
|
||||
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
<p>
|
||||
English | <a href="README.md">中文</a>
|
||||
</p>
|
||||
---
|
||||
title: Bot Detect
|
||||
keywords: [higress, bot detect]
|
||||
description: Bot detect plugin configuration reference
|
||||
---
|
||||
## Function Description
|
||||
The `bot-detect` plugin can be used to identify and block internet crawlers from accessing site resources.
|
||||
|
||||
# Description
|
||||
`bot-detect` plugin can be used to identify and prevent web crawlers from crawling websites.
|
||||
## Running Properties
|
||||
Plugin Execution Phase: `Authorization Phase`
|
||||
Plugin Execution Priority: `310`
|
||||
|
||||
# Configuration Fields
|
||||
## Configuration Fields
|
||||
| Name | Data Type | Required | Default Value | Description |
|
||||
| ----------------- | ------------------- | --------------| --------------| ---------------------------------------------------------- |
|
||||
| allow | array of string | Optional | - | Regular expressions to match the User-Agent request header; requests matching will be allowed to access. |
|
||||
| deny | array of string | Optional | - | Regular expressions to match the User-Agent request header; requests matching will be blocked. |
|
||||
| blocked_code | number | Optional | 403 | HTTP status code returned when a request is blocked. |
|
||||
| blocked_message | string | Optional | - | HTTP response body returned when a request is blocked. |
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| allow | array of string | Optional | - | A regular expression to match the User-Agent request header and will allow access if the match hits |
|
||||
| deny | array of string | Optional | - | A regular expression to match the User-Agent request header and will block the request if the match hits |
|
||||
| blocked_code | number | Optional | 403 | The HTTP status code returned when a request is blocked |
|
||||
| blocked_message | string | Optional | - | The HTTP response Body returned when a request is blocked |
|
||||
The `allow` and `deny` fields can both be left unconfigured, in which case the default crawler identification logic will be executed. Configuring the `allow` field can allow requests that would otherwise hit the default crawler identification logic. Configuring the `deny` field can add additional crawler identification logic.
|
||||
|
||||
If field `allow` and field `deny` are not configured at the same time, the default logic to identify crawlers will be executed. By configuring the `allow` field, requests that would otherwise hit the default logic can be allowed. The judgement can be extended by configuring the `deny` field
|
||||
|
||||
The default set of crawler judgment regular expressions is as follows:
|
||||
The default crawler identification regular expression set is as follows:
|
||||
|
||||
```bash
|
||||
# Bots General matcher 'name/0.0'
|
||||
@@ -33,45 +37,23 @@ The default set of crawler judgment regular expressions is as follows:
|
||||
(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\/\$BotVersion|123metaspider-Bot|1470\.net crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\b\w{0,30}favicon\w{0,30}\b|\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|Tiny Tiny RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s|) Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+)|)|)|)
|
||||
```
|
||||
|
||||
# Configuration Samples
|
||||
|
||||
## Release Requests that would otherwise Hit the Crawler Rules
|
||||
## Configuration Example
|
||||
### Allowing Requests That Hit the Crawler Rules
|
||||
```yaml
|
||||
allow:
|
||||
- ".*Go-http-client.*"
|
||||
```
|
||||
|
||||
Without this configuration, the default Golang web library request will be treated as a crawler and access will be denied.
|
||||
If this configuration is not made, requests from the default Golang network library will be treated as crawlers and blocked.
|
||||
|
||||
|
||||
## Add Crawler Judgement
|
||||
### Adding Crawler Identification
|
||||
```yaml
|
||||
deny:
|
||||
- "spd-tools.*"
|
||||
```
|
||||
|
||||
According to this configuration, the following requests will be denied:
|
||||
|
||||
With this configuration, the following requests will be blocked:
|
||||
```bash
|
||||
curl http://example.com -H 'User-Agent: spd-tools/1.1'
|
||||
curl http://exmaple.com -H 'User-Agent: spd-tools'
|
||||
```
|
||||
|
||||
## Only Enabled for Specific Routes or Domains
|
||||
```yaml
|
||||
# Use _rules_ field for fine-grained rule configurations
|
||||
_rules_:
|
||||
# Rule 1: Match by route name
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
# Rule 2: Match by domain
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- ".*Go-http-client.*"
|
||||
```
|
||||
In the rule sample of `_match_route_`, `route-a` and `route-b` are the route names provided when creating a new gateway route. When the current route names matches the configuration, the rule following shall be applied.
|
||||
In the rule sample of `_match_domain_`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied.
|
||||
All rules shall be checked following the order of items in the `_rules_` field, The first matched rule will be applied. All remained will be ignored.
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
<p>
|
||||
<a href="README_EN.md"> English </a> | 中文
|
||||
</p>
|
||||
---
|
||||
title: 自定义应答
|
||||
keywords: [higress,customn response]
|
||||
description: 自定义应答插件配置参考
|
||||
---
|
||||
|
||||
# 功能说明
|
||||
|
||||
## 功能说明
|
||||
`custom-response`插件支持配置自定义的响应,包括自定义 HTTP 应答状态码、HTTP 应答头,以及 HTTP 应答 Body。可以用于 Mock 响应,也可以用于判断特定状态码后给出自定义应答,例如在触发网关限流策略时实现自定义响应。
|
||||
|
||||
# 配置字段
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`认证阶段`
|
||||
插件执行优先级:`910`
|
||||
|
||||
## 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
@@ -14,9 +22,9 @@
|
||||
| body | string | 选填 | - | 自定义 HTTP 应答 Body |
|
||||
| enable_on_status | array of number | 选填 | - | 匹配原始状态码,生成自定义响应,不填写时,不判断原始状态码 |
|
||||
|
||||
# 配置示例
|
||||
## 配置示例
|
||||
|
||||
## Mock 应答场景
|
||||
### Mock 应答场景
|
||||
|
||||
```yaml
|
||||
status_code: 200
|
||||
@@ -38,7 +46,7 @@ Content-Length: 17
|
||||
{"hello":"world"}
|
||||
```
|
||||
|
||||
## 触发限流时自定义响应
|
||||
### 触发限流时自定义响应
|
||||
|
||||
```yaml
|
||||
enable_on_status:
|
||||
@@ -58,27 +66,3 @@ Location: https://example.com
|
||||
从而实现基于浏览器 302 重定向机制,将限流后的用户引导到其他页面,比如可以是一个 CDN 上的静态页面。
|
||||
|
||||
如果希望触发限流时,正常返回其他应答,参考 Mock 应答场景配置相应的字段即可。
|
||||
|
||||
## 对特定路由或域名开启
|
||||
```yaml
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
body: "{\"hello\":\"world\"}"
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
enable_on_status:
|
||||
- 429
|
||||
status_code: 200
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
body: "{\"errmsg\": \"rate limited\"}"
|
||||
```
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
|
||||
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
|
||||
|
||||
@@ -1,84 +1,54 @@
|
||||
<p>
|
||||
English | <a href="README.md">中文</a>
|
||||
</p>
|
||||
---
|
||||
title: Custom Response
|
||||
keywords: [higress, custom response]
|
||||
description: Custom response plugin configuration reference
|
||||
---
|
||||
## Function Description
|
||||
The `custom-response` plugin supports the configuration of custom responses, including custom HTTP response status codes, HTTP response headers, and HTTP response bodies. It can be used for Mock responses or for providing custom responses based on specific status codes, such as implementing custom responses when triggering the gateway rate-limiting policy.
|
||||
|
||||
# Description
|
||||
`custom-response` plugin implements a function of sending custom responses, including custom HTTP response status codes, HTTP response headers and HTTP response body, which can be used in the scenarios of response mocking and sending a custom response for specific status codes, such as customizing the response for rate-limited requests.
|
||||
## Running Attributes
|
||||
Plugin Execution Phase: `Authentication Phase`
|
||||
|
||||
# Configuration Fields
|
||||
Plugin Execution Priority: `910`
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
## Configuration Fields
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| status_code | number | Optional | 200 | Custom HTTP response status code |
|
||||
| headers | array of string | Optional | - | Custom HTTP response header. Key and value shall be separated using `=`. |
|
||||
| headers | array of string | Optional | - | Custom HTTP response headers, keys and values separated by `=` |
|
||||
| body | string | Optional | - | Custom HTTP response body |
|
||||
| enable_on_status | array of number | Optional | - | The original response status code to match. Generate the custom response only the actual status code matches the configuration. Ignore the status code match if left unconfigured. |
|
||||
|
||||
# Configuration Samples
|
||||
|
||||
## Mock Responses
|
||||
| enable_on_status | array of number | Optional | - | Match original status codes to generate custom responses; if not specified, the original status code is not checked |
|
||||
|
||||
## Configuration Example
|
||||
### Mock Response Scenario
|
||||
```yaml
|
||||
status_code: 200
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
- Hello=World
|
||||
body: "{\"hello\":\"world\"}"
|
||||
|
||||
```
|
||||
|
||||
According to the configuration above, all the requests will get the following custom response:
|
||||
|
||||
With this configuration, the request will return the following custom response:
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Hello: World
|
||||
Content-Length: 17
|
||||
|
||||
{"hello":"world"}
|
||||
```
|
||||
|
||||
## Send a Custom Response when Rate-Limited
|
||||
|
||||
### Custom Response on Rate Limiting
|
||||
```yaml
|
||||
enable_on_status:
|
||||
enable_on_status:
|
||||
- 429
|
||||
status_code: 302
|
||||
headers:
|
||||
- Location=https://example.com
|
||||
```
|
||||
|
||||
When rate-limited, normally gateway will return a status code of `429` . Now, rate-limited requests will get the following custom response:
|
||||
|
||||
When the gateway rate limiting is triggered, it generally returns the `429` status code, and the request will return the following custom response:
|
||||
```text
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://example.com
|
||||
```
|
||||
This achieves the goal of redirecting users who have been rate-limited to another page based on the browser's 302 redirect mechanism, which could be a static page on a CDN.
|
||||
|
||||
So based on the 302 redirecting mechanism provided by browsers, this can redirect rate-limited users to other pages, for example, a static page hosted on CDN.
|
||||
|
||||
If you'd like to send other responses when rate-limited, please add other fields into the configuration, referring to the Mock Responses scenario.
|
||||
|
||||
## Only Enabled for Specific Routes or Domains
|
||||
```yaml
|
||||
# Use _rules_ field for fine-grained rule configurations
|
||||
_rules_:
|
||||
# Rule 1: Match by route name
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
body: "{\"hello\":\"world\"}"
|
||||
# Rule 2: Match by domain
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
enable_on_status:
|
||||
- 429
|
||||
status_code: 200
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
body: "{\"errmsg\": \"rate limited\"}"
|
||||
```
|
||||
In the rule sample of `_match_route_`, `route-a` and `route-b` are the route names provided when creating a new gateway route. When the current route names matches the configuration, the rule following shall be applied.
|
||||
In the rule sample of `_match_domain_`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied.
|
||||
All rules shall be checked following the order of items in the `_rules_` field, The first matched rule will be applied. All remained will be ignored.
|
||||
If you wish to return other responses normally when rate limiting is triggered, just refer to the Mock response scenario to configure the relevant fields accordingly.
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
# 功能说明
|
||||
---
|
||||
title: HMAC 认证
|
||||
keywords: [higress,hmac auth]
|
||||
description: HMAC 认证插件配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
`hmac-auth`插件实现了基于 HMAC 算法为 HTTP 请求生成不可伪造的签名,并基于签名实现身份认证和鉴权
|
||||
|
||||
# 配置字段
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`认证阶段`
|
||||
插件执行优先级:`330`
|
||||
|
||||
## 配置字段
|
||||
|
||||
**注意:**
|
||||
|
||||
- 在一个规则里,鉴权配置和认证配置不可同时存在
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
|
||||
### 认证配置
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ------------- | --------------- | -------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `global_auth` | bool | 选填(**仅实例级别配置**) | - | 只能在实例级别配置,若配置为true,则全局生效认证机制; 若配置为false,则只对做了配置的域名和路由生效认证机制,若不配置则仅当没有域名和路由配置时全局生效(兼容老用户使用习惯)。 |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
| `date_offset` | number | 选填 | - | 配置允许的客户端最大时间偏移,单位为秒,根据请求头`Date`解析客户端 UTC 时间,可用于避免请求重放;未配置时,不做校验 |
|
||||
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
|
||||
|
||||
|
||||
`consumers`中每一项的配置字段说明如下:
|
||||
|
||||
@@ -17,24 +36,21 @@
|
||||
| `secret` | string | 必填 | - | 配置用于生成签名的secret |
|
||||
| `name` | string | 必填 | - | 配置该consumer的名称 |
|
||||
|
||||
`_rules_` 中每一项的配置字段说明如下:
|
||||
### 鉴权配置(非必需)
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
|
||||
| `_match_domain_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的域名 |
|
||||
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求,配置允许访问的consumer名称 |
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `allow` | array of string | 选填(**非实例级别配置**) | - | 只能在路由或域名等细粒度规则上配置,对于符合匹配条件的请求,配置允许访问的 consumer,从而实现细粒度的权限控制 |
|
||||
|
||||
**注意:**
|
||||
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
|
||||
# 配置示例
|
||||
## 配置示例
|
||||
|
||||
以下配置将对网关特定路由或域名开启 Hmac Auth 认证和鉴权,注意`key`字段不能重复
|
||||
### 全局配置认证和路由粒度进行鉴权
|
||||
|
||||
在实例级别做如下插件配置, 注意`key`字段不能重复:
|
||||
|
||||
## 对特定路由或域名开启
|
||||
```yaml
|
||||
global_auth: false
|
||||
consumers:
|
||||
- key: appKey-example-1
|
||||
secret: appSecret-example-1
|
||||
@@ -42,34 +58,33 @@ consumers:
|
||||
- key: appKey-example-2
|
||||
secret: appSecret-example-2
|
||||
name: consumer-2
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer-1
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer-2
|
||||
```
|
||||
每条匹配规则下的`allow`字段用于指定该匹配条件下允许访问的调用者列表;
|
||||
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name`为`consumer-1`的调用者访问,其他调用者不允许访问;
|
||||
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name`为`consumer-2`的调用者访问,其他调用者不允许访问;
|
||||
|
||||
认证成功后,请求的header中会被添加一个`X-Mse-Consumer`字段,其值为调用方的名称,例如`consumer-1`。
|
||||
|
||||
## 网关实例级别开启
|
||||
|
||||
以下配置将对网关实例级别开启 Hamc Auth 认证
|
||||
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的调用者访问,其他调用者不被允许访问。
|
||||
|
||||
|
||||
### 网关实例级别开启
|
||||
|
||||
以下配置将对网关实例级别开启 Hamc Auth 认证,所有请求均需要经过认证后才能访问。
|
||||
|
||||
```yaml
|
||||
global_auth: true
|
||||
consumers:
|
||||
- key: appKey-example-1
|
||||
secret: appSecret-example-1
|
||||
@@ -80,18 +95,18 @@ consumers:
|
||||
```
|
||||
|
||||
|
||||
# 签名机制说明
|
||||
## 签名机制说明
|
||||
|
||||
## 配置准备
|
||||
### 配置准备
|
||||
|
||||
如上指引,在插件配置中配置生成和验证签名需要用的凭证配置
|
||||
|
||||
- key: 用于请求头 `x-ca-key` 中设置
|
||||
- secret: 用于生成请求签名
|
||||
|
||||
## 客户端签名生成方式
|
||||
### 客户端签名生成方式
|
||||
|
||||
### 流程简介
|
||||
#### 流程简介
|
||||
|
||||
客户端生成签名一共分三步处理:
|
||||
|
||||
@@ -104,7 +119,7 @@ consumers:
|
||||
如下图所示:
|
||||

|
||||
|
||||
### 签名串提取流程
|
||||
#### 签名串提取流程
|
||||
|
||||
客户端需要从Http请求中提取出关键数据,组合成一个签名串,生成的签名串的格式如下:
|
||||
|
||||
@@ -160,7 +175,7 @@ Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN +
|
||||
|
||||
4. Query和Form存在数组参数时(key相同,value不同的参数) ,取第一个Value参与签名计算
|
||||
|
||||
### 签名串提取示例
|
||||
#### 签名串提取示例
|
||||
|
||||
初始的HTTP请求:
|
||||
```text
|
||||
@@ -190,7 +205,7 @@ x-ca-timestamp:1525872629832
|
||||
/http2test/test?param1=test&password=123456789&username=xiaoming
|
||||
```
|
||||
|
||||
### 签名计算流程
|
||||
#### 签名计算流程
|
||||
|
||||
客户端从HTTP请求中提取出关键数据组装成签名串后,需要对签名串进行加密及编码处理,形成最终的签名
|
||||
|
||||
@@ -206,7 +221,7 @@ String sign = Base64.encodeBase64String(result);
|
||||
|
||||
总结一下,就是将 `stringToSign` 使用UTF-8解码后得到Byte数组,然后使用加密算法对Byte数组进行加密,然后使用Base64算法进行编码,形成最终的签名。
|
||||
|
||||
### 添加签名流程
|
||||
#### 添加签名流程
|
||||
|
||||
客户端需要将以下四个Header放在HTTP请求中传输给API网关,进行签名校验:
|
||||
|
||||
@@ -238,9 +253,9 @@ content-length:33
|
||||
username=xiaoming&password=123456789
|
||||
```
|
||||
|
||||
## 服务端签名验证方式
|
||||
### 服务端签名验证方式
|
||||
|
||||
### 流程简介
|
||||
#### 流程简介
|
||||
|
||||
服务器验证客户端签名一共分四步处理:
|
||||
|
||||
@@ -256,7 +271,7 @@ username=xiaoming&password=123456789
|
||||

|
||||
|
||||
|
||||
## 签名排错方法
|
||||
### 签名排错方法
|
||||
|
||||
网关签名校验失败时,会将服务端的签名串(StringToSign)放到HTTP Response的Header中返回到客户端,Key为:X-Ca-Error-Message,用户只需要将本地计算的签名串(StringToSign)与服务端返回的签名串进行对比即可找到问题;
|
||||
|
||||
@@ -269,7 +284,7 @@ X-Ca-Error-Message: Server StringToSign:`GET#application/json##application/json
|
||||
|
||||
```
|
||||
|
||||
# 相关错误码
|
||||
## 相关错误码
|
||||
|
||||
| HTTP 状态码 | 出错信息 | 原因说明 |
|
||||
| ----------- | ---------------------- | -------------------------------------------------------------------------------- |
|
||||
@@ -281,5 +296,3 @@ X-Ca-Error-Message: Server StringToSign:`GET#application/json##application/json
|
||||
| 413 | Request Body Too Large | 请求 Body 超过限制大小:32 MB |
|
||||
| 413 | Payload Too Large | 请求 Body 超过全局配置 DownstreamConnectionBufferLimits |
|
||||
| 403 | Unauthorized Consumer | 请求的调用方无访问权限 |
|
||||
|
||||
|
||||
|
||||
@@ -1,76 +1,71 @@
|
||||
# Function Description
|
||||
The `hmac-auth` plugin implements the generation of tamper-proof signatures for HTTP requests based on HMAC algorithm, and uses the signature for identity authentication and authorization.
|
||||
---
|
||||
title: HMAC Authentication
|
||||
keywords: [higress,hmac auth]
|
||||
description: HMAC Authentication plugin configuration reference
|
||||
---
|
||||
## Function Description
|
||||
The `hmac-auth` plugin implements the generation of tamper-proof signatures for HTTP requests based on the HMAC algorithm, and performs authentication and authorization based on the signature.
|
||||
|
||||
# Configuration Fields
|
||||
## Running Attributes
|
||||
Plugin execution phase: `Authentication phase`
|
||||
Plugin execution priority: `330`
|
||||
|
||||
| Name | Data Type | Required | Default | Description |
|
||||
| ------------- | --------------- | -------------| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `consumers` | array of object | Required | - | Configures the caller of the service to authenticate the request. |
|
||||
| `date_offset` | number | Optional | - | Configures the maximum allowed time deviation of the client, in seconds. It is used to parse the client's UTC time from `the Date` header of the request, and can be used to prevent replay attacks. If not configured, no validation is performed. |
|
||||
| `_rules_` | array of object | Optional | - | Configures the access control list for specific routes or domains, used for authorization of requests. |
|
||||
## Configuration Fields
|
||||
**Note:**
|
||||
- In a rule, authentication and authorization configurations cannot coexist.
|
||||
- For requests that pass authentication and authorization, the request header will be added with an `X-Mse-Consumer` field to identify the caller's name.
|
||||
|
||||
The configuration fields for each item in `consumers` are as follows :
|
||||
### Authentication Configuration
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
| ------------- | ---------------- | ------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `global_auth` | bool | Optional (**Instance level configuration only**) | - | Can only be configured at the instance level. If set to true, it acts globally; if false, only applies to configured domains and routes. If not configured, it will apply globally only when there are no domain and route configurations (to accommodate old user habits). |
|
||||
| `consumers` | array of object | Mandatory | - | Configures the callers of the service for request authentication. |
|
||||
| `date_offset` | number | Optional | - | Configures the maximum allowed client time offset, in seconds; parsed based on the request header `Date`; can be used to prevent request replay; no validation is performed if not configured. |
|
||||
|
||||
| Name | Data Type| Required | Default| Description |
|
||||
| -------- | -------- | ------------ | ------ | ----------------------------------------------------------------------- |
|
||||
| `key` | string | Required | - | Configures the key extracted from the `x-ca-key` header of the request. |
|
||||
| `secret` | string | Required | - | Configures the secret used to generate the signature. |
|
||||
| `name` | string | Required | - | Configures the name of the consumer. |
|
||||
The configuration fields for each item in `consumers` are as follows:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
| -------- | --------- | ----------- | ------------- | ------------------------------------------- |
|
||||
| `key` | string | Mandatory | - | Configures the key extracted from the `x-ca-key` header of the request. |
|
||||
| `secret` | string | Mandatory | - | Configures the secret used to generate the signature. |
|
||||
| `name` | string | Mandatory | - | Configures the name of the consumer. |
|
||||
|
||||
The configuration fields for each item in `_rules_` are as follows:
|
||||
### Authorization Configuration (Optional)
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
| ----------- | ---------------- | --------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `allow` | array of string | Optional (**Non-instance level configuration**) | - | Can only be configured on granular rules such as routes or domains. For requests that match the conditions, configure the allowed consumers to achieve fine-grained permission control. |
|
||||
|
||||
| Name | Data Type | Required | Default | Description |
|
||||
| ---------------- | --------------- | ------------------------------------------------- | ---------------------------- | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | Optional, either `_match_route_` or `_match_domain_` must be provided | - | Configures the name of the route to match. |
|
||||
| `_match_domain_` | array of string | Optional, either `_match_route_` or `_match_domain_` must be provided | - | Configures the name of the domain to match. |
|
||||
| `allow` | array of string | Required | - | Configures the name of the consumer to allow for requests that match the specified route or domain. |
|
||||
|
||||
**Note:**
|
||||
- If `_rules_` is not configured, authentication is enabled for all routes on the current gateway instance by default ;
|
||||
- For requests that pass authentication and authorization, a `X-Mse-Consumer` header will be added to the request headers to identify the name of the consumer.
|
||||
|
||||
# Configuration Example
|
||||
|
||||
The following configuration enables Hmac Auth authentication and authorization for specific routes or domains on the gateway. Note that the `key` field should not be duplicated.
|
||||
|
||||
## Enabling for specific routes or domains
|
||||
## Configuration Example
|
||||
### Global Configuration Authentication and Route Granular Authorization
|
||||
Configure the following plugin settings at the instance level. Note that the `key` field cannot be duplicated:
|
||||
```yaml
|
||||
consumers:
|
||||
global_auth: false
|
||||
consumers:
|
||||
- key: appKey-example-1
|
||||
secret: appSecret-example-1
|
||||
name: consumer-1
|
||||
- key: appKey-example-2
|
||||
secret: appSecret-example-2
|
||||
name: consumer-2
|
||||
# Configuring Fine-Grained Rules using _rules_ Field
|
||||
_rules_:
|
||||
# Rule 1: Matching by route name.
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer-1
|
||||
# Rule 2: Applies based on domain name matching.
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer-2
|
||||
```
|
||||
The `allow` field under each matching rule specifies the list of callers allowed to access under that matching condition;
|
||||
|
||||
In this example, `route-a` and `route-b` specified in `_match_route_` are the route names filled in when creating the gateway route. When either of these routes is matched, it will allow access to the caller named `consumer-1`, while denying access to other callers;
|
||||
|
||||
In` _match_domain_`, `*.example.com` and `test.com` are used to match the requested domain name. When a match is found, it will allow access to the caller named `consumer-2`, while denying access to other callers;
|
||||
|
||||
Upon successful authentication, the `X-Mse-Consumer` field will be added to the request header with the value set to the caller's name, such as `consumer-1`.。
|
||||
|
||||
## Enable at the Gateway Instance Level
|
||||
|
||||
The following configuration enables HMAC authentication at the gateway instance level.
|
||||
|
||||
For route-a and route-b, configure the plugin as follows:
|
||||
```yaml
|
||||
consumers:
|
||||
allow:
|
||||
- consumer1
|
||||
```
|
||||
For the two domains *.example.com and test.com, configure as follows:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
If configured in the console, the specified route names route-a and route-b correspond to the route names filled in when creating the gateway routes. When matched to these two routes, access will be allowed for the caller named consumer1, while other callers will not be allowed access.
|
||||
|
||||
The specified *.example.com and test.com are used to match the domains of the requests. When a domain match is found, access will be allowed for the caller named consumer2, while other callers will not be allowed access.
|
||||
|
||||
### Gateway Instance Level Activation
|
||||
The following configuration will enable HMAC Auth authentication at the gateway instance level, requiring all requests to undergo authentication before access.
|
||||
```yaml
|
||||
global_auth: true
|
||||
consumers:
|
||||
- key: appKey-example-1
|
||||
secret: appSecret-example-1
|
||||
name: consumer-1
|
||||
@@ -79,34 +74,24 @@ consumers:
|
||||
name: consumer-2
|
||||
```
|
||||
|
||||
## Signature Mechanism Description
|
||||
### Configuration Preparation
|
||||
As mentioned above, configure the credentials required for generating and verifying signatures in the plugin settings.
|
||||
- key: to be set in the request header `x-ca-key`.
|
||||
- secret: used for generating request signatures.
|
||||
|
||||
# Description of Signing Mechanism
|
||||
### Client Signature Generation Process
|
||||
#### Overview
|
||||
The client generates a signature through three main steps:
|
||||
1. Extract key data from the original request to create a string for signing.
|
||||
2. Encrypt the key data signing string using the algorithm and the configured `secret` to obtain the signature.
|
||||
3. Include all relevant headers for the signature into the original HTTP request to form the final HTTP request.
|
||||
|
||||
## Configuration Preparation
|
||||
|
||||
As mentioned in the guide above, configure the credential settings required for generating and validating signatures in the plugin configuration.
|
||||
|
||||
- key: Used for setting in the request header `x-ca-key`.
|
||||
- secret: Used for generating the request signature.
|
||||
|
||||
## Client Signature Generation Method
|
||||
### Overview of the Process
|
||||
|
||||
The process for generating a signature on the client side consists of three steps:
|
||||
|
||||
1. Extracting key data from the original request to obtain a string to be signed.
|
||||
|
||||
2. Using encryption algorithms and the configured `secret` to encrypt the key data signing string and obtain a signature.
|
||||
|
||||
3. Adding all headers related to the signature to the original HTTP request to obtain the final HTTP request.
|
||||
|
||||
As shown below :
|
||||
As shown in the figure below:
|
||||

|
||||
|
||||
### Process for Extracting Signing String
|
||||
|
||||
To generate a signature, the client needs to extract key data from the HTTP request and combine it into a signing string. The format of the generated signing string is as follows:
|
||||
|
||||
#### Signing String Extraction Process
|
||||
The client needs to extract key data from the HTTP request, combine it into a signing string, which has the following format:
|
||||
```text
|
||||
HTTPMethod
|
||||
Accept
|
||||
@@ -116,54 +101,38 @@ Date
|
||||
Headers
|
||||
PathAndParameters
|
||||
```
|
||||
|
||||
The signing string consists of the above 7 fields separated by \n. If Headers is empty, no \n is needed. If other fields are empty, the \n should still be retained. The signature is case-sensitive. Below are the rules for extracting each field:
|
||||
|
||||
- HTTPMethod: The HTTP method used in the request, in all capital letters, such as POST.
|
||||
|
||||
- Accept: The value of the Accept header in the request, which can be empty. It is recommended to explicitly set the Accept header. When Accept is empty, some HTTP clients will set the default value of `*/*`, which may cause signature verification to fail.
|
||||
|
||||
- Content-MD5: The value of the Content-MD5 header in the request, which can be empty. It is only calculated when there is a non-form body in the request. The following is a reference calculation method for Content-MD5 values in :
|
||||
|
||||
|
||||
The seven fields above constitute the entire signing string, separated by newline characters `\n`. If Headers is empty, no newline is needed; other fields should retain `\n` if empty. The signature is case-sensitive. Below are the extraction rules for each field:
|
||||
- HTTPMethod: The HTTP method, all uppercase (e.g., POST).
|
||||
- Accept: The value of the Accept header in the request, can be empty. It is recommended to explicitly set the Accept Header. When Accept is empty, some HTTP clients may set a default value of `*/*`, resulting in a signature verification failure.
|
||||
- Content-MD5: The value of the Content-MD5 header in the request, can be empty. It is calculated only if there is a Body in the request and it is not in Form format. Here’s a reference calculation method for the Content-MD5 value in Java:
|
||||
```java
|
||||
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));
|
||||
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getBytes("UTF-8")));
|
||||
```
|
||||
|
||||
- Content-Type: The value of the Content-Type header in the request, which can be empty.
|
||||
|
||||
- Date: The value of the Date header in the request. When the` date_offset` configuration is not enabled, it can be empty. Otherwise, it will be used for time offset verification.
|
||||
|
||||
- Headers: Users can select specific headers to participate in the signature. There are the following rules for concatenating the signature string with headers:
|
||||
- The keys of the headers participating in the signature calculation are sorted in alphabetical order and concatenated as follows:
|
||||
- Content-Type: The value of the Content-Type header in the request, can be empty.
|
||||
- Date: The value of the Date header in the request. If the `date_offset` configuration is not turned on, it can be empty; otherwise, it will be used for time offset verification.
|
||||
- Headers: Users can select specific headers to participate in the signature. The rules for concatenating the signing header string are as follows:
|
||||
- The Keys of the headers participating in the signature calculation are concatenated after being sorted lexicographically, as follows:
|
||||
```text
|
||||
HeaderKey1 + ":" + HeaderValue1 + "\n"\+
|
||||
HeaderKey2 + ":" + HeaderValue2 + "\n"\+
|
||||
HeaderKey1 + ":" + HeaderValue1 + "\n" +
|
||||
HeaderKey2 + ":" + HeaderValue2 + "\n" +
|
||||
...
|
||||
HeaderKeyN + ":" + HeaderValueN + "\n"
|
||||
```
|
||||
- If the value of a header is empty, it will participate in the signature with the `HeaderKey+":"+"\n"` only, and the key and english colon should be retained.
|
||||
- The set of keys for all headers participating in the signature is separated by a comma and placed in the `X-Ca-Signature-Headers header`.
|
||||
- If the Value of a certain header is empty, use HeaderKey + ":" + "\n" to participate in the signature, retaining the Key and the colon.
|
||||
- The collection of all participating header Keys is placed in the Header with the key X-Ca-Signature-Headers, separated by commas.
|
||||
- The following headers are not included in the header signature calculation: X-Ca-Signature, X-Ca-Signature-Headers, Accept, Content-MD5, Content-Type, Date.
|
||||
|
||||
- PathAndParameters: This field contains all parameters in the path, query, and form. The specific format is as follows:
|
||||
|
||||
- PathAndParameters: This field includes Path, Query, and all parameters in Form, specifically organized as follows:
|
||||
```text
|
||||
Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueN
|
||||
```
|
||||
Note:
|
||||
1. The Key of Query and Form parameters should be sorted lexicographically before being concatenated as above.
|
||||
2. If Query and Form parameters are empty, just use Path without adding `?`.
|
||||
3. If the Value of parameters is empty, only the Key should be retained in the signature, the equal sign does not need to be added.
|
||||
4. In the case of array parameters (parameters with the same key but different values), only the first Value should be used for signature calculation.
|
||||
|
||||
Notes:
|
||||
1. The keys of the query and form parameter pairs are sorted alphabetically, and the same format as above is used for concatenation.
|
||||
|
||||
2. If there are no query and form parameters, use the path directly without adding `?` .
|
||||
|
||||
3. If the value of a parameter is empty, only the key will be included in the signature. The equal sign should not be included in the signature.
|
||||
|
||||
4. If there are array parameters in the query or form (parameters with the same key but different values), only the first value should be included in the signature calculation.
|
||||
|
||||
### Example of Extracting Signing String
|
||||
|
||||
The initial HTTP request :
|
||||
#### Signing String Extraction Example
|
||||
Initial HTTP request:
|
||||
```text
|
||||
POST /http2test/test?param1=test HTTP/1.1
|
||||
host:api.aliyun.com
|
||||
@@ -177,8 +146,7 @@ x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
|
||||
content-length:33
|
||||
username=xiaoming&password=123456789
|
||||
```
|
||||
|
||||
The correct generated signature string is :
|
||||
The generated correct signing string is:
|
||||
```text
|
||||
POST
|
||||
application/json; charset=utf-8
|
||||
@@ -190,13 +158,10 @@ x-ca-signature-method:HmacSHA256
|
||||
x-ca-timestamp:1525872629832
|
||||
/http2test/test?param1=test&password=123456789&username=xiaoming
|
||||
```
|
||||
#### Signature Calculation Process
|
||||
After the client assembles the key data extracted from the HTTP request into a signing string, it needs to encrypt the signing string and encode it to form the final signature.
|
||||
|
||||
### Signature Calculation Process
|
||||
|
||||
After extracting the key data from the HTTP request and assembling it into a signature string, the client needs to encrypt and encode the signature string to form the final signature.
|
||||
|
||||
The specific encryption format is as follows, where `stringToSign` is the extracted signature string, `secret` is the one filled in the plugin configuration, and `sign` is the final generated signature:
|
||||
|
||||
The specific encryption form is as follows, where `stringToSign` is the extracted signing string, `secret` is the one filled in the plugin configuration, and `sign` is the final generated signature:
|
||||
```java
|
||||
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
|
||||
byte[] secretBytes = secret.getBytes("UTF-8");
|
||||
@@ -204,23 +169,16 @@ hmacSha256.init(new SecretKeySpec(secretBytes, 0, secretBytes.length, "HmacSHA25
|
||||
byte[] result = hmacSha256.doFinal(stringToSign.getBytes("UTF-8"));
|
||||
String sign = Base64.encodeBase64String(result);
|
||||
```
|
||||
To summarize, the `stringToSign` is decoded using UTF-8 to obtain a Byte array, then the encryption algorithm is applied to the Byte array, and finally, the Base64 algorithm is used for encoding, forming the final signature.
|
||||
|
||||
In summary, the `stringToSign` is decoded using UTF-8 to obtain a Byte array. Then, an encryption algorithm is used to encrypt the Byte array, and finally, the Base64 algorithm is used to encode the encrypted data, resulting in the final signature.
|
||||
|
||||
### The Process of Adding a Signature
|
||||
|
||||
The client needs to include the following four headers in the HTTP request to be transmitted to the API gateway for signature verification:
|
||||
|
||||
- x-ca-key: The value is the APP Key and is required.
|
||||
|
||||
- x-ca-signature-method: The signature algorithm, the value can be HmacSHA256 or HmacSHA1, optional. The default value is HmacSHA256.
|
||||
|
||||
- x-ca-signature-headers: The collection of keys for all signature headers, separated by commas. Optional.
|
||||
|
||||
- x-ca-signature: The signature and it is required.
|
||||
|
||||
Here is an example of a complete HTTP request with a signature :
|
||||
#### Adding the Signature Process
|
||||
The client needs to include the following four headers in the HTTP request to transmit to the API gateway for signature verification:
|
||||
- x-ca-key: The APP Key, mandatory.
|
||||
- x-ca-signature-method: The signature algorithm, can be HmacSHA256 or HmacSHA1, optional, default is HmacSHA256.
|
||||
- x-ca-signature-headers: The collection of all signature header Keys, separated by commas, optional.
|
||||
- x-ca-signature: The signature, mandatory.
|
||||
|
||||
Below is an example of the entire HTTP request carrying the signature:
|
||||
```text
|
||||
POST /http2test/test?param1=test HTTP/1.1
|
||||
host:api.aliyun.com
|
||||
@@ -239,48 +197,35 @@ content-length:33
|
||||
username=xiaoming&password=123456789
|
||||
```
|
||||
|
||||
## Server-side Signature Verification Method
|
||||
### Server-side Signature Verification Method
|
||||
#### Overview
|
||||
The server verifies the client signature through four main steps:
|
||||
1. Extract key data from the received request to create a signing string.
|
||||
2. Read the `key` from the received request and query the corresponding `secret`.
|
||||
3. Encrypt the key data signing string using the algorithm and the `secret` to obtain the signature.
|
||||
4. Read the client signature from the received request and compare the server-side signature with the client-side signature for consistency.
|
||||
|
||||
### Overview of the Process
|
||||
|
||||
The server-side signature verification of the client's request involves four steps :
|
||||
|
||||
1. Extract crucial data from the received request to obtain a string for signing.
|
||||
|
||||
2. Retrieve the `key` from the received request and use it to query its corresponding `secret`.
|
||||
|
||||
3. Encrypt the string for signing using the encryption algorithm and `secret`.
|
||||
|
||||
4. Retrieve the client's signature from the received request, and compare the consistency of the server-side signature with the client's signature.
|
||||
|
||||
As shown below :
|
||||
As shown in the figure below:
|
||||

|
||||
|
||||
### Signature Troubleshooting Method
|
||||
When the gateway signature verification fails, the server's signing string (StringToSign) will be returned in the HTTP Response header to the client, with the key: X-Ca-Error-Message. The user only needs to compare the locally computed signing string (StringToSign) with the signing string returned by the server to find the issue.
|
||||
|
||||
## Troubleshooting Signature Errors
|
||||
|
||||
When the gateway signature verification fails, the server-side signing string (StringToSign) will be returned to the client in the HTTP Response Header. The key is X-Ca-Error-Message. Users only need to compare the locally calculated signing string with the server-side signing string returned to locate the problem;
|
||||
|
||||
If the StringToSign on the server side is consistent with that on the client side, please check whether the APP Secret used for signature calculation is correct;
|
||||
|
||||
Because line breaks cannot be represented in HTTP headers, all line breaks in the StringToSign are replaced with #, as shown below:
|
||||
If the StringToSign from the server and the client are consistent, please check whether the APP Secret used for signature calculation is correct.
|
||||
|
||||
Since HTTP headers cannot express line breaks, the line breaks in the StringToSign have been replaced with `#`, as shown below:
|
||||
```text
|
||||
X-Ca-Error-Message: Server StringToSign:`GET#application/json##application/json##X-Ca-Key:200000#X-Ca-Timestamp:1589458000000#/app/v1/config/keys?keys=TEST`
|
||||
|
||||
```
|
||||
|
||||
# Related Error Codes
|
||||
|
||||
| HTTP Status Code | Error Message | Reason |
|
||||
| ----------- | ---------------------- | -------------------------------------------------------------------------------- |
|
||||
| 401 | Invalid Key | The x-ca-key request header is not provided or is invalid. |
|
||||
| 401 | Empty Signature | The x-ca-signature request header does not contain a signature. |
|
||||
| 400 | Invalid Signature | The x-ca-signature request header contains a signature that does not match the server-calculated signature. |
|
||||
| 400 | Invalid Content-MD5 | The content-md5 request header is incorrect. |
|
||||
| 400 | Invalid Date | The time offset calculated based on the date request header exceeds the configured date_offset. |
|
||||
| 413 | Request Body Too Large | The request body exceeds the size limit of 32 MB. |
|
||||
| 413 | Payload Too Large | The request body exceeds the DownstreamConnectionBufferLimits global configuration. |
|
||||
| 403 | Unauthorized Consumer | The requesting party does not have access permission. |
|
||||
|
||||
|
||||
## Related Error Codes
|
||||
| HTTP Status Code | Error Message | Reasoning |
|
||||
| ---------------- | --------------------- | --------------------------------------------- |
|
||||
| 401 | Invalid Key | The request header did not provide x-ca-key, or x-ca-key is invalid. |
|
||||
| 401 | Empty Signature | The request header did not provide the x-ca-signature signing string. |
|
||||
| 400 | Invalid Signature | The x-ca-signature signing string in the request header does not match the signature calculated by the server. |
|
||||
| 400 | Invalid Content-MD5 | The Content-MD5 header in the request is incorrect. |
|
||||
| 400 | Invalid Date | The time offset calculated based on the Date header in the request exceeds the configured date_offset. |
|
||||
| 413 | Request Body Too Large| The request Body exceeds the maximum size of 32 MB. |
|
||||
| 413 | Payload Too Large | The request Body exceeds the global configured DownstreamConnectionBufferLimits. |
|
||||
| 403 | Unauthorized Consumer | The calling party does not have access permissions for the request. |
|
||||
|
||||
@@ -1,17 +1,216 @@
|
||||
# 功能说明
|
||||
---
|
||||
title: JWT 认证
|
||||
keywords: [higress,jwt auth]
|
||||
description: JWT 认证插件配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
`jwt-auth`插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能,支持从HTTP请求的URL参数、请求头、Cookie字段解析JWT,同时验证该Token是否有权限访问。
|
||||
|
||||
本插件和`安全能力->认证鉴权`中JWT认证的区别是,额外提供了调用方身份识别的能力,支持对不同调用方配置不同的JWT凭证。
|
||||
|
||||
# 详细说明
|
||||
## 运行属性
|
||||
|
||||
## 1、基于token的认证
|
||||
插件执行阶段:`认证阶段`
|
||||
插件执行优先级:`340`
|
||||
|
||||
### 1.1 简介
|
||||
## 配置字段
|
||||
|
||||
**注意:**
|
||||
|
||||
- 在一个规则里,鉴权配置和认证配置不可同时存在
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
|
||||
### 认证配置
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `global_auth` | bool | 选填(**仅实例级别配置**) | - | 只能在实例级别配置,若配置为true,则全局生效认证机制; 若配置为false,则只对做了配置的域名和路由生效认证机制,若不配置则仅当没有域名和路由配置时全局生效(兼容老用户使用习惯)。 |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
|
||||
`consumers`中每一项的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------------------- | ----------------- | -------- | ------------------------------------------------- | ------------------------ |
|
||||
| `name` | string | 必填 | - | 配置该consumer的名称 |
|
||||
| `jwks` | string | 必填 | - | https://www.rfc-editor.org/rfc/rfc7517 指定的json格式字符串,是由验证JWT中签名的公钥(或对称密钥)组成的Json Web Key Set |
|
||||
| `issuer` | string | 必填 | - | JWT的签发者,需要和payload中的iss字段保持一致 |
|
||||
| `claims_to_headers` | array of object | 选填 | - | 抽取JWT的payload中指定字段,设置到指定的请求头中转发给后端 |
|
||||
| `from_headers` | array of object | 选填 | {"name":"Authorization","value_prefix":"Bearer "} | 从指定的请求头中抽取JWT |
|
||||
| `from_params` | array of string | 选填 | access_token | 从指定的URL参数中抽取JWT |
|
||||
| `from_cookies` | array of string | 选填 | - | 从指定的cookie中抽取JWT |
|
||||
| `clock_skew_seconds` | number | 选填 | 60 | 校验JWT的exp和iat字段时允许的时钟偏移量,单位为秒 |
|
||||
| `keep_token` | bool | 选填 | ture | 转发给后端时是否保留JWT |
|
||||
|
||||
**注意:**
|
||||
- 只有当`from_headers`,`from_params`,`from_cookies`均未配置时,才会使用默认值
|
||||
|
||||
`from_headers` 中每一项的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求| 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------- | ------ | --------------------------------------------------------- |
|
||||
| `name` | string | 必填 | - | 抽取JWT的请求header |
|
||||
| `value_prefix` | string | 必填 | - | 对请求header的value去除此前缀,剩余部分作为JWT |
|
||||
|
||||
`claims_to_headers` 中每一项的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求| 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------- | ------ | --------------------------------------------------------- |
|
||||
| `claim` | string | 必填 | - | JWT payload中的指定字段,要求必须是字符串或无符号整数类型 |
|
||||
| `header` | string | 必填 | - | 从payload取出字段的值设置到这个请求头中,转发给后端 |
|
||||
| `override` | bool | 选填 | true | true时,存在同名请求头会进行覆盖;false时,追加同名请求头 |
|
||||
|
||||
|
||||
### 鉴权配置(非必需)
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `allow` | array of string | 选填(**非实例级别配置**) | - | 只能在路由或域名等细粒度规则上配置,对于符合匹配条件的请求,配置允许访问的 consumer,从而实现细粒度的权限控制 |
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 全局配置认证和路由粒度进行鉴权
|
||||
|
||||
注意如果一个JWT能匹配多个`jwks`,则按照配置顺序命中第一个匹配的`consumer`
|
||||
|
||||
在实例级别做如下插件配置:
|
||||
|
||||
```yaml
|
||||
global_auth: false
|
||||
consumers:
|
||||
- name: consumer1
|
||||
issuer: abcd
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"kid": "123",
|
||||
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
|
||||
"alg": "HS256"
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: consumer2
|
||||
issuer: abc
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "123",
|
||||
"alg": "RS256",
|
||||
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
对 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的调用者访问,其他调用者不被允许访问。
|
||||
|
||||
根据该配置,下列请求可以允许访问:
|
||||
|
||||
假设以下请求会匹配到route-a这条路由
|
||||
|
||||
**将 JWT 设置在 url 参数中**
|
||||
```bash
|
||||
curl 'http://xxx.hello.com/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
**将 JWT 设置在 http 请求头中**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
认证鉴权通过后,请求的header中会被添加一个`X-Mse-Consumer`字段,在此例中其值为`consumer1`,用以标识调用方的名称
|
||||
|
||||
下列请求将拒绝访问:
|
||||
|
||||
**请求未提供JWT,返回401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test
|
||||
```
|
||||
|
||||
**根据请求提供的JWT匹配到的调用者无访问权限,返回403**
|
||||
```bash
|
||||
# consumer1不在*.example.com的allow列表里
|
||||
curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
#### 网关实例级别开启
|
||||
|
||||
以下配置将对网关实例级别开启 JWT Auth 认证,所有请求均需要经过认证后才能访问。
|
||||
|
||||
```yaml
|
||||
global_auth: true
|
||||
consumers:
|
||||
- name: consumer1
|
||||
issuer: abcd
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"kid": "123",
|
||||
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
|
||||
"alg": "HS256"
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: consumer2
|
||||
issuer: abc
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "123",
|
||||
"alg": "RS256",
|
||||
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 常见错误码说明
|
||||
|
||||
| HTTP 状态码 | 出错信息 | 原因说明 |
|
||||
| ----------- | ---------------------- | -------------------------------------------------------------------------------- |
|
||||
| 401 | Jwt missing | 请求头未提供JWT |
|
||||
| 401 | Jwt expired | JWT已经过期 |
|
||||
| 401 | Jwt verification fails | JWT payload校验失败,如iss不匹配 |
|
||||
| 403 | Access Denied | 无权限访问当前路由 |
|
||||
|
||||
## 详细说明
|
||||
|
||||
### 1、基于token的认证
|
||||
|
||||
#### 1.1 简介
|
||||
|
||||
很多对外开放的API需要识别请求者的身份,并据此判断所请求的资源是否可以返回给请求者。token就是一种用于身份验证的机制,基于这种机制,应用不需要在服务端保留用户的认证信息或者会话信息,可实现无状态、分布式的Web应用授权,为应用的扩展提供了便利。
|
||||
|
||||
### 1.2 流程描述
|
||||
#### 1.2 流程描述
|
||||
|
||||

|
||||
|
||||
@@ -35,13 +234,13 @@
|
||||
|
||||
在这个整个过程中, 网关利用token认证机制,实现了用户使用自己的用户体系对自己API进行授权的能力。下面我们就要介绍网关实现token认证所使用的结构化令牌Json Web Token(JWT)。
|
||||
|
||||
### 1.3 JWT
|
||||
#### 1.3 JWT
|
||||
|
||||
#### 1.3.1 简介
|
||||
##### 1.3.1 简介
|
||||
|
||||
Json Web Toke(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准RFC7519。JWT一般可以用作独立的身份验证令牌,可以包含用户标识、用户角色和权限等信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,特别适用于分布式站点的登录场景。
|
||||
|
||||
#### 1.3.2 JWT的构成
|
||||
##### 1.3.2 JWT的构成
|
||||
|
||||
`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ`
|
||||
|
||||
@@ -119,11 +318,11 @@ var signature = HMACSHA256(encodedString, '$secret');
|
||||
将这三部分用 . 连接成一个完整的字符串,就构成了 1.3.2 节最开始的JWT示例。
|
||||
|
||||
|
||||
#### 1.3.3 时效
|
||||
##### 1.3.3 时效
|
||||
|
||||
网关会验证token中的exp字段,一旦这个字段过期了,网关会认为这个token无效而将请求直接打回。过期时间这个值必须设置。
|
||||
|
||||
#### 1.3.4 JWT的几个特点
|
||||
##### 1.3.4 JWT的几个特点
|
||||
|
||||
1. JWT 默认是不加密,不能将秘密数据写入 JWT。
|
||||
2. JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
|
||||
@@ -131,9 +330,9 @@ var signature = HMACSHA256(encodedString, '$secret');
|
||||
4. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
|
||||
5. 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用HTTPS 协议传输。
|
||||
|
||||
## 2、用户系统如何应用JWT插件保护API
|
||||
### 2、用户系统如何应用JWT插件保护API
|
||||
|
||||
### 2.1 生成一对JWK(JSON Web 密钥)
|
||||
#### 2.1 生成一对JWK(JSON Web 密钥)
|
||||
|
||||
**方法一、在线生成:**
|
||||
|
||||
@@ -162,7 +361,7 @@ final String publicKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLeve
|
||||
final String privateKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
|
||||
```
|
||||
|
||||
### 2.2 使用JWK中的私钥实现颁发token 的认证服务
|
||||
#### 2.2 使用JWK中的私钥实现颁发token 的认证服务
|
||||
|
||||
需要使用2.1节中在线生成的 Keypair JSON字符串(三个方框内的第一个)或者本地生成的 privateKeyString JSON字符串作为私钥来颁发token,用于授权可信的用户访问受保护的API,具体实现可以参考下方示例。 向客户颁发token的形式由用户根据具体的业务场景决定,可以将颁发token的功能部署到生产环境,配置成普通API后由访问者通过用户名密码获得,也可以直接在本地环境生成token 后,直接拷贝给指定用户使用。
|
||||
|
||||
@@ -216,187 +415,3 @@ public class GenerateJwtDemo {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# 插件配置说明
|
||||
|
||||
## 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
|
||||
|
||||
`consumers`中每一项的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------------------- | ----------------- | -------- | ------------------------------------------------- | ------------------------ |
|
||||
| `name` | string | 必填 | - | 配置该consumer的名称 |
|
||||
| `jwks` | string | 必填 | - | https://www.rfc-editor.org/rfc/rfc7517 指定的json格式字符串,是由验证JWT中签名的公钥(或对称密钥)组成的Json Web Key Set |
|
||||
| `issuer` | string | 必填 | - | JWT的签发者,需要和payload中的iss字段保持一致 |
|
||||
| `claims_to_headers` | array of object | 选填 | - | 抽取JWT的payload中指定字段,设置到指定的请求头中转发给后端 |
|
||||
| `from_headers` | array of object | 选填 | {"name":"Authorization","value_prefix":"Bearer "} | 从指定的请求头中抽取JWT |
|
||||
| `from_params` | array of string | 选填 | access_token | 从指定的URL参数中抽取JWT |
|
||||
| `from_cookies` | array of string | 选填 | - | 从指定的cookie中抽取JWT |
|
||||
| `clock_skew_seconds` | number | 选填 | 60 | 校验JWT的exp和iat字段时允许的时钟偏移量,单位为秒 |
|
||||
| `keep_token` | bool | 选填 | ture | 转发给后端时是否保留JWT |
|
||||
|
||||
**注意:**
|
||||
- 只有当`from_headers`,`from_params`,`from_cookies`均未配置时,才会使用默认值
|
||||
|
||||
`from_headers` 中每一项的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求| 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------- | ------ | --------------------------------------------------------- |
|
||||
| `name` | string | 必填 | - | 抽取JWT的请求header |
|
||||
| `value_prefix` | string | 必填 | - | 对请求header的value去除此前缀,剩余部分作为JWT |
|
||||
|
||||
`claims_to_headers` 中每一项的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求| 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------- | ------ | --------------------------------------------------------- |
|
||||
| `claim` | string | 必填 | - | JWT payload中的指定字段,要求必须是字符串或无符号整数类型 |
|
||||
| `header` | string | 必填 | - | 从payload取出字段的值设置到这个请求头中,转发给后端 |
|
||||
| `override` | bool | 选填 | true | true时,存在同名请求头会进行覆盖;false时,追加同名请求头 |
|
||||
|
||||
|
||||
`_rules_` 中每一项的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
|
||||
| `_match_domain_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的域名 |
|
||||
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求,配置允许访问的consumer名称 |
|
||||
|
||||
**注意:**
|
||||
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 对特定路由或域名开启
|
||||
|
||||
以下配置将对网关特定路由或域名开启 Jwt Auth 认证和鉴权,注意如果一个JWT能匹配多个`jwks`,则按照配置顺序命中第一个匹配的`consumer`
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- name: consumer1
|
||||
issuer: abcd
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"kid": "123",
|
||||
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
|
||||
"alg": "HS256"
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: consumer2
|
||||
issuer: abc
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "123",
|
||||
"alg": "RS256",
|
||||
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
|
||||
}
|
||||
]
|
||||
}
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer1
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name`为`consumer1`的调用者访问,其他调用者不允许访问;
|
||||
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name`为`consumer2`的调用者访问,其他调用者不允许访问。
|
||||
|
||||
#### 根据该配置,下列请求可以允许访问:
|
||||
|
||||
假设以下请求会匹配到route-a这条路由
|
||||
|
||||
**将 JWT 设置在 url 参数中**
|
||||
```bash
|
||||
curl 'http://xxx.hello.com/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
**将 JWT 设置在 http 请求头中**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
认证鉴权通过后,请求的header中会被添加一个`X-Mse-Consumer`字段,在此例中其值为`consumer1`,用以标识调用方的名称
|
||||
|
||||
#### 下列请求将拒绝访问:
|
||||
|
||||
**请求未提供JWT,返回401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test
|
||||
```
|
||||
|
||||
**根据请求提供的JWT匹配到的调用者无访问权限,返回403**
|
||||
```bash
|
||||
# consumer1不在*.example.com的allow列表里
|
||||
curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
### 网关实例级别开启
|
||||
|
||||
以下配置未指定`_rules_`字段,因此将对网关实例级别开启 JWT Auth 认证
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- name: consumer1
|
||||
issuer: abcd
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"kid": "123",
|
||||
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
|
||||
"alg": "HS256"
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: consumer2
|
||||
issuer: abc
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "123",
|
||||
"alg": "RS256",
|
||||
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# 常见错误码说明
|
||||
|
||||
| HTTP 状态码 | 出错信息 | 原因说明 |
|
||||
| ----------- | ---------------------- | -------------------------------------------------------------------------------- |
|
||||
| 401 | Jwt missing | 请求头未提供JWT |
|
||||
| 401 | Jwt expired | JWT已经过期 |
|
||||
| 401 | Jwt verification fails | JWT payload校验失败,如iss不匹配 |
|
||||
| 403 | Access Denied | 无权限访问当前路由 |
|
||||
|
||||
|
||||
@@ -1,170 +1,306 @@
|
||||
# Description
|
||||
The `jwt-auth` plugin implements authentication and authorization based on JWT (JSON Web Token), supports parsing JWTs from URL parameters, request headers, and Cookie fields from HTTP requests, and verifies whether the token has permission to access.
|
||||
---
|
||||
title: JWT Authentication
|
||||
keywords: [higress,jwt auth]
|
||||
description: JWT Authentication plugin configuration reference
|
||||
---
|
||||
|
||||
The difference between this plugin and the JWT authentication in `Security Capabilities->Authentication and Authorization` is that it provides additional capabilities for identifying the caller's identity, supporting the configuration of different JWT credentials for different callers.
|
||||
## Function Description
|
||||
`jwt-auth` plugin implements authentication and authorization based on JWT (JSON Web Tokens). It supports extracting JWT from HTTP request URL parameters, request headers, and Cookie fields, while verifying whether the Token has the necessary permissions to access the resource.
|
||||
|
||||
# Detailed Description
|
||||
The difference between this plugin and the JWT authentication in `Security Capability -> Authentication and Authorization` is that it additionally provides the capability of identifying the caller's identity, supporting different JWT credentials for different callers.
|
||||
|
||||
## 1. Token-based authentication
|
||||
## Runtime Properties
|
||||
Plugin execution phase: `Authentication Phase`
|
||||
Plugin execution priority: `340`
|
||||
|
||||
### 1.1 Introduction
|
||||
Many open APIs need to identify the identity of the caller and determine whether the requested resource can be returned to the caller based on this identity. Token is a mechanism used for identity verification. Based on this mechanism, the application does not need to retain the user's authentication information or session information on the server, which can realize stateless and distributed web application authorization and provide convenience for application extension.
|
||||
## Configuration Fields
|
||||
**Note:**
|
||||
- In one rule, authentication configuration and authorization configuration cannot coexist.
|
||||
- For requests authenticated through authentication and authorization, the request header will be augmented with an `X-Mse-Consumer` field to identify the caller's name.
|
||||
|
||||
### 1.2 Process Description
|
||||
### Authentication Configuration
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ----------- | --------------- | ---------------------------------------------- | ------------- | ----------------------------------------------------------- |
|
||||
| `global_auth` | bool | Optional (**instance-level configuration only**) | - | Can only be configured at the instance level. If set to true, it will globally enable the authentication mechanism; if set to false, it will only apply to the configured domain names and routes. If not configured, it will only globally take effect when no domain names and routes are configured (to be compatible with old user habits). |
|
||||
| `consumers` | array of object | Required | - | Configure service consumers for request authentication |
|
||||
|
||||

|
||||
The configuration fields for each item in `consumers` are as follows:
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ----------------------- | ------------------ | ------------ | -------------------------------------------------- | ------------------------------- |
|
||||
| `name` | string | Required | - | The name of the consumer |
|
||||
| `jwks` | string | Required | - | JSON format string specified by https://www.rfc-editor.org/rfc/rfc7517, consisting of the public key (or symmetric key) used to verify the JWT signature. |
|
||||
| `issuer` | string | Required | - | The issuer of the JWT, must match the `iss` field in the payload. |
|
||||
| `claims_to_headers` | array of object | Optional | - | Extract the specified fields from the JWT payload and set them in the specified request headers to forward to the backend. |
|
||||
| `from_headers` | array of object | Optional | {"name":"Authorization","value_prefix":"Bearer "} | Extract JWT from the specified request headers. |
|
||||
| `from_params` | array of string | Optional | access_token | Extract JWT from the specified URL parameters. |
|
||||
| `from_cookies` | array of string | Optional | - | Extract JWT from the specified cookies. |
|
||||
| `clock_skew_seconds` | number | Optional | 60 | The allowed clock skew when validating the `exp` and `iat` fields of the JWT, measured in seconds. |
|
||||
| `keep_token` | bool | Optional | true | Whether to retain the JWT when forwarding to the backend. |
|
||||
|
||||
The above figure is the business process sequence diagram when the gateway uses JWT for authentication, and the following we will describe the steps marked in the figure in detail in words:
|
||||
**Note:**
|
||||
- The default values will only be used when `from_headers`, `from_params`, and `from_cookies` are not all configured.
|
||||
The configuration fields for each item in `from_headers` are as follows:
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| --------------- | ---------------- | ------------ | ------------- | ----------------------------------------------- |
|
||||
| `name` | string | Required | - | Extract JWT from the request header. |
|
||||
| `value_prefix` | string | Required | - | Remove the prefix from the request header value, with the remaining part serving as the JWT. |
|
||||
|
||||
1. The client initiates an authentication request to the API gateway, usually carrying the end user's username and password in the request;
|
||||
The configuration fields for each item in `claims_to_headers` are as follows:
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| --------------- | ---------------- | ------------ | ------------- | --------------------------------------------- |
|
||||
| `claim` | string | Required | - | The specified field in the JWT payload, must be a string or unsigned integer type. |
|
||||
| `header` | string | Required | - | The value of the field extracted from the payload is set to this request header and forwarded to the backend. |
|
||||
| `override` | bool | Optional | true | If true, existing headers with the same name will be overridden; if false, they will be appended. |
|
||||
|
||||
2. The gateway forwards the request directly to the backend service;
|
||||
### Authorization Configuration (Optional)
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ----------- | --------------- | ---------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `allow` | array of string | Optional (**not instance-level configuration**) | - | Can only be configured on fine-grained rules such as routes or domain names, allowing specified consumers to access matching requests for fine-grained permission control. |
|
||||
|
||||
3. The backend service reads the authentication information (such as the username and password) in the request for verification. After the verification is passed, it uses the private key to generate a standard token and returns it to the gateway;
|
||||
## Configuration Examples
|
||||
### Global Configuration for Authentication and Route-Level Authorization
|
||||
Note: If a JWT matches multiple `jwks`, the first matching consumer will be applied according to the configuration order.
|
||||
|
||||
4. The gateway returns the response with the token to the client, and the client needs to cache this token locally;
|
||||
|
||||
5. The client sends a business request to the API gateway, carrying the token in the request;
|
||||
|
||||
6. The gateway uses the public key set by the user to verify the token in the request. After the verification is passed, the request is passed through to the backend service;
|
||||
|
||||
7. The backend service processes the business and responds;
|
||||
|
||||
8. The gateway returns the business response to the client.
|
||||
|
||||
In this entire process, the gateway uses the token authentication mechanism to realize the ability of users to use their own user system to authorize their API. Next, we will introduce the structured token JSON Web Token (JWT) used by the gateway to implement token authentication.
|
||||
|
||||
### 1.3 JWT
|
||||
|
||||
#### 1.3.1 Introduction
|
||||
|
||||
JSON Web Token (JWT) is an open standard RFC7519 based on JSON for executing a type of claim to pass between network applications. JWT can generally be used as an independent identity verification token, which can contain user identification, user roles, and permissions information, making it easier to obtain resources from the resource server, and can also add some other necessary declarative information for other business logic, especially suitable for the login scenario of distributed sites.
|
||||
|
||||
#### 1.3.2 Composition of JWT
|
||||
|
||||
`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ`
|
||||
|
||||
As shown in the example above, JWT is a string consisting of three parts:
|
||||
|
||||
- Header
|
||||
- Payload
|
||||
- Signature
|
||||
|
||||
**Header**
|
||||
|
||||
The header of the JWT carries two pieces of information:
|
||||
|
||||
- The type of the token, which is JWT
|
||||
- The algorithm used for encryption
|
||||
|
||||
The gateway supports the following encryption algorithms:
|
||||
|
||||
```text
|
||||
ES256, ES384, ES512,
|
||||
HS256, HS384, HS512,
|
||||
RS256, RS384, RS512,
|
||||
PS256, PS384, PS512,
|
||||
EdDSA
|
||||
Configure the plugin at the instance level as follows:
|
||||
```yaml
|
||||
global_auth: false
|
||||
consumers:
|
||||
- name: consumer1
|
||||
issuer: abcd
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"kid": "123",
|
||||
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
|
||||
"alg": "HS256"
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: consumer2
|
||||
issuer: abc
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "123",
|
||||
"alg": "RS256",
|
||||
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The complete header looks like the following JSON:
|
||||
Configure the following for routes `route-a` and `route-b`:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer1
|
||||
```
|
||||
|
||||
Configure the following for domain names `*.example.com` and `test.com`:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
The specified `route-a` and `route-b` refer to the routing names filled in when creating the gateway route. When these two routes are matched, the caller with the name consumer1 will be allowed access, while others will not be permitted.
|
||||
|
||||
The specified `*.example.com` and `test.com` are used to match the request domain names. When a matching domain name is found, the caller with the name consumer2 will be allowed access, while others will not.
|
||||
|
||||
Based on this configuration, the following requests will be allowed:
|
||||
|
||||
Suppose the following request matches the route-a.
|
||||
**Setting the JWT in URL parameters**
|
||||
```bash
|
||||
curl 'http://xxx.hello.com/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
**Setting the JWT in HTTP request headers**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
After successful authentication and authorization, the request's header will include an `X-Mse-Consumer` field, in this example with the value `consumer1`, to identify the caller's name.
|
||||
|
||||
The following requests will be denied:
|
||||
**Request without JWT returns 401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test
|
||||
```
|
||||
|
||||
**Caller matching from the provided JWT has no access permission, returning 403**
|
||||
```bash
|
||||
# consumer1 is not in the allow list for *.example.com
|
||||
curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
#### Enable at Gateway Instance Level
|
||||
The following configuration will enable JWT Auth authentication at the instance level, requiring all requests to be authenticated before accessing.
|
||||
```yaml
|
||||
global_auth: true
|
||||
consumers:
|
||||
- name: consumer1
|
||||
issuer: abcd
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"kid": "123",
|
||||
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
|
||||
"alg": "HS256"
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: consumer2
|
||||
issuer: abc
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "123",
|
||||
"alg": "RS256",
|
||||
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Common Error Codes
|
||||
| HTTP Status Code | Error Message | Reason Description |
|
||||
| ---------------- | ----------------------------- | ---------------------------------------------------------------------------- |
|
||||
| 401 | Jwt missing | The request header did not provide a JWT |
|
||||
| 401 | Jwt expired | The JWT has expired |
|
||||
| 401 | Jwt verification fails | JWT payload verification failed, such as mismatched `iss` |
|
||||
| 403 | Access Denied | No permission to access the current route |
|
||||
|
||||
## Detailed Description
|
||||
### 1. Token-based Authentication
|
||||
#### 1.1 Introduction
|
||||
Many open APIs need to identify the requester's identity and determine whether the requested resource can be returned. A token is a mechanism used for identity verification. With this mechanism, applications do not need to retain user authentication information or session information on the server, allowing for stateless, distributed web application authorization, facilitating application scaling.
|
||||
|
||||
#### 1.2 Process Description
|
||||

|
||||
The above image shows the entire business process sequence diagram for gateway authentication using JWT. Below, we will detail the steps indicated in the diagram:
|
||||
|
||||
1. The client initiates an authentication request to the API gateway, generally carrying the terminal user's username and password.
|
||||
2. The gateway forwards the request directly to the backend service.
|
||||
3. The backend service reads the verification information in the request (such as username and password) for validation. Upon successful verification, it generates a standard token using a private key and returns it to the gateway.
|
||||
4. The gateway returns a response containing the token to the client, who must cache this token locally.
|
||||
5. The client sends a business request to the API gateway, carrying the token in the request.
|
||||
6. The gateway validates the token using the user's set public key. Upon successful validation, it forwards the request to the backend service.
|
||||
7. The backend service processes the business and responds.
|
||||
8. The gateway returns the business response to the client.
|
||||
|
||||
Throughout this process, the gateway utilizes the token authentication mechanism, enabling the user to authorize their API using their user system. Next, we will introduce the structured token used by the gateway for token authentication: JSON Web Token (JWT).
|
||||
|
||||
#### 1.3 JWT
|
||||
##### 1.3.1 Introduction
|
||||
JSON Web Token (JWT) is an open standard (RFC7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. JWT can be used as a stand-alone authentication token, capable of containing user identity, user roles, permissions, and other information, aiding in resource retrieval from resource servers and adding any additional claims required by business logic, particularly suitable for login scenarios for distributed sites.
|
||||
|
||||
##### 1.3.2 JWT Structure
|
||||
`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ`
|
||||
As shown above, JWT is a string composed of three parts:
|
||||
- Header
|
||||
- Payload
|
||||
- Signature
|
||||
|
||||
**Header**
|
||||
The header of the JWT carries two pieces of information:
|
||||
- Token type, which is JWT
|
||||
- Signing algorithm
|
||||
|
||||
The supported signing algorithms by the gateway are as follows:
|
||||
```text
|
||||
ES256, ES384, ES512,
|
||||
HS256, HS384, HS512,
|
||||
RS256, RS384, RS512,
|
||||
PS256, PS384, PS512,
|
||||
EdDSA
|
||||
```
|
||||
|
||||
The complete header looks like the following JSON:
|
||||
```js
|
||||
{
|
||||
'typ': 'JWT',
|
||||
'alg': 'HS256'
|
||||
}
|
||||
```
|
||||
|
||||
The header is then Base64-encoded (this encoding can be symmetrically decoded), forming the first part:
|
||||
|
||||
`eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9`
|
||||
|
||||
**Payload**
|
||||
|
||||
The payload is where the actual information is stored. The details are defined below:
|
||||
```
|
||||
Then the header is Base64 encoded (this encoding is symmetrically decodable), forming the first part.
|
||||
`eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9`
|
||||
|
||||
**Payload**
|
||||
The payload is where valid information is stored. Its details are defined as follows:
|
||||
```text
|
||||
iss: The issuer of the token. This indicates who created the token and is a string value.
|
||||
sub: The subject identifier. This is the unique identifier for the end user provided by the issuer, and is no longer than 255 ASCII characters, and is case-sensitive within the issuer's scope.
|
||||
aud: The audience(s) of the token, which is an array of case-sensitive strings.
|
||||
exp: The expiration time of the token, after which the token will be invalidated, is an integer declaration representing the Unix timestamp in seconds.
|
||||
iat: The issuance time of the token, is an integer declaration representing the Unix timestamp in seconds.
|
||||
jti: The unique identifier of the token, and the value is unique for every token created by the issuer. It is usually a cryptographically random value to prevent conflicts. This component adds a random entropy that an attacker cannot obtain to the structured token, making it more difficult for the token to be guessed or replayed.
|
||||
```
|
||||
|
||||
Custom fields for a user feature can also be added, such as the example below adding a "name" field for the user's nickname:
|
||||
|
||||
iss: Token issuer. Indicates who created the token, this claim is a string.
|
||||
sub: Subject Identifier, an identifier provided by the issuer for its end users, unique within the issuer's scope, up to 255 ASCII characters, case sensitive.
|
||||
aud: Audience(s), the audience of the token, an array of strings that are case-sensitive.
|
||||
exp: Expiration time, a timestamp of the token's expiration. Tokens expired beyond this time will be invalid. This claim is an integer, representing the number of seconds since January 1, 1970.
|
||||
iat: Issuance time of the token, this claim is an integer, representing the number of seconds since January 1, 1970.
|
||||
jti: Unique identifier for the token, the value of this claim must be unique for each token created by the token issuer to prevent collisions. It is typically a cryptographically random value. This value adds a random entropy component to the structured token that is not accessible to an attacker, helping prevent token guess and replay attacks.
|
||||
```
|
||||
Custom fields necessary for the user system can also be added, for example, the following example adds a nickname `name`:
|
||||
```js
|
||||
{
|
||||
"sub": "1234567890",
|
||||
"name": "John Doe"
|
||||
}
|
||||
```
|
||||
|
||||
The payload is then Base64-encoded, forming the second part of the JWT:
|
||||
|
||||
`JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE`
|
||||
|
||||
**Signature**
|
||||
|
||||
This part is a string that consists of the Base64-encoded header and Base64-encoded payload concatenated with a period, followed by the encryption of the resulting string using the algorithm specified in the header (where $secret represents the user's private key).
|
||||
```
|
||||
Then encode it in Base64 to obtain the second part of the JWT:
|
||||
`JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE`
|
||||
|
||||
**Signature**
|
||||
This part requires the Base64-encoded Header and Base64-encoded Payload to be connected by a period, and then encrypted using the signing method declared in the Header (where `$secret` represents the user's private key) to form the third part of the JWT.
|
||||
```js
|
||||
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
|
||||
var signature = HMACSHA256(encodedString, '$secret');
|
||||
```
|
||||
```
|
||||
Connecting these three parts with a period creates a complete string that forms the JWT example at the beginning of section 1.3.2.
|
||||
|
||||
These three parts are then concatenated using periods to form the complete JWT string as shown in the example at the beginning of this section.
|
||||
##### 1.3.3 Validity Period
|
||||
The gateway will validate the `exp` field in the token. Once this field expires, the gateway will consider this token invalid and directly reject the request. The expiration time must be set.
|
||||
|
||||
#### 1.3.3 Time validity
|
||||
##### 1.3.4 Characteristics of JWT
|
||||
1. JWT is not encrypted by default; do not write secret data into JWT.
|
||||
2. JWT can be used for both authentication and information exchange. Effectively using JWT can reduce the number of queries to the database on the server.
|
||||
3. The biggest drawback of JWT is that since the server does not maintain session state, it cannot revoke a token during use or change the permissions of said token. In other words, once a JWT is issued, it will remain valid until expiration, unless the server implements additional logic.
|
||||
4. JWT itself contains authentication information, and once leaked, anyone can gain all permissions of that token. To minimize theft, the validity period of JWT should be set to be relatively short. For some critical permissions, users should be re-authenticated.
|
||||
5. To reduce theft, JWT should not be transmitted in plain text over HTTP but should use HTTPS for transmission.
|
||||
|
||||
The gateway will verify the exp field in the token. Once this field has expired, the gateway will consider the token invalid and reject the request directly. The expiration time value must be set.
|
||||
|
||||
#### 1.3.4 Several Characteristics of JWT
|
||||
|
||||
1. By default, JWT is not encrypted and cannot write secret data into JWT.
|
||||
2. JWT can not only be used for authentication but also for exchanging information. Using JWT effectively can reduce the number of times servers query the database.
|
||||
3. The biggest drawback of JWT is that the server cannot invalidate a token during use, or change the token's permission, because the server does not keep the session state. That is, once JWT is issued, it will always be valid before it expires, unless the server deploys additional logic.
|
||||
4. JWT contains authentication information itself. Once leaked, anyone can obtain all permissions of the token. To reduce theft, the expiration time of JWT should be set relatively short. For some more important permissions, users should be authenticated again when using them.
|
||||
5. To reduce theft, JWT should not be transmitted in plaintext using the HTTP protocol, and the HTTPS protocol should be used for transmission.
|
||||
|
||||
## 2. How to apply the JWT plugin to protect the API of the user system
|
||||
|
||||
### 2.1 Generate a pair of JWK (JSON Web Key)
|
||||
|
||||
**Method 1: Generate online**
|
||||
|
||||
Users can generate the private and public keys used for token generation and verification on this website https://mkjwk.org. The private key is used for the authorization service to issue JWT, and the public key is configured into the JWT plugin for the gateway to verify the signature of the request. Note that the JWKs format configuration used by the gateway requires the public key in the figure below to be placed in the keys structure, such as: `{"keys":[{"kty":"RSA","e":"AQAB",...}]}`
|
||||
|
||||
<img src="https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/2336348951/p135823.png" style="zoom:50%" />
|
||||
|
||||
**Method 2: Generate locally**
|
||||
|
||||
This article uses a Java example to illustrate, and users of other languages can also find relevant tools to generate key pairs. Create a new Maven project and add the following dependencies:
|
||||
### 2. How User Systems Apply the JWT Plugin to Protect APIs
|
||||
#### 2.1 Generating a Pair of JWKs (JSON Web Keys)
|
||||
**Method 1: Online Generation:**
|
||||
Users can generate the private and public keys used for token generation and verification at this site https://mkjwk.org. The private key is used by the authorization service to issue JWTs, and the public key is configured into the JWT plugin for the gateway to verify requests. Pay attention to the jwks format configuration used by the gateway. In the image below, the Public Key should be placed into the keys structure, for example: `{"keys":[{"kty":"RSA","e":"AQAB",...}]}`
|
||||
<img src="https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/2336348951/p135823.png" style="zoom:50%" />
|
||||
|
||||
**Method 2: Local Generation:**
|
||||
This article demonstrates using Java; users of other languages can find related tools to generate key pairs. Create a new Maven project and include the following dependency:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.bitbucket.b_c</groupId>
|
||||
<artifactId>jose4j</artifactId>
|
||||
<version>0.7.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Use the following code to generate a pair of RSA keys:
|
||||
|
||||
```
|
||||
Use the following code to generate a pair of RSA keys:
|
||||
```java
|
||||
RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
|
||||
final String publicKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);
|
||||
final String privateKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
|
||||
```
|
||||
|
||||
### 2.2 Implement the token issuance authentication service using the private key in JWK
|
||||
|
||||
The Keypair JSON string generated online in Section 2.1 (the first of the three boxes) or privateKeyString JSON string generated locally needs to be used as the private key to issue tokens for trusted users to access protected APIs. The specific implementation can refer to the following example. The form of issuing tokens to customers is determined by the user according to the specific business scenario. The function of issuing tokens can be deployed in the production environment and configured as a normal API for visitors to obtain through username and password, or tokens can be generated directly in the local environment and copied to designated users for use.
|
||||
```
|
||||
|
||||
#### 2.2 Using the Private Key in JWK to Implement the Token Issuance Authentication Service
|
||||
You will need to use the Keypair JSON string (the first inside the three boxes) generated online in section 2.1 or the locally generated privateKeyString JSON string as the private key to issue tokens for authorizing trusted users to access protected APIs. The specific implementation can refer to the example below. The form of issuing tokens to customers can be determined by the user based on the specific business scenario; it can be deployed in the production environment, configured to be a normal API that visitors can access through username and password, or tokens can be generated locally and directly copied for specified users to use.
|
||||
```java
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PrivateKey;
|
||||
import org.jose4j.json.JsonUtil;
|
||||
import org.jose4j.jwk.RsaJsonWebKey;
|
||||
import org.jose4j.jwk.RsaJwkGenerator;
|
||||
@@ -173,32 +309,29 @@ import org.jose4j.jws.JsonWebSignature;
|
||||
import org.jose4j.jwt.JwtClaims;
|
||||
import org.jose4j.jwt.NumericDate;
|
||||
import org.jose4j.lang.JoseException;
|
||||
|
||||
public class GenerateJwtDemo {
|
||||
public static void main(String[] args) throws JoseException {
|
||||
String keyId = "uniq_key";
|
||||
//Use the Keypair generated in section 2.1 of this article
|
||||
// Use the Keypair generated in section 2.1
|
||||
String privateKeyJson = "{\n"
|
||||
+ " \"kty\": \"RSA\",\n"
|
||||
+ " \"d\": "
|
||||
+
|
||||
"\"O9MJSOgcjjiVMNJ4jmBAh0mRHF_TlaVva70Imghtlgwxl8BLfcf1S8ueN1PD7xV6Cnq8YenSKsfiNOhC6yZ_fjW1syn5raWfj68eR7cjHWjLOvKjwVY33GBPNOvspNhVAFzeqfWneRTBbga53Agb6jjN0SUcZdJgnelzz5JNdOGaLzhacjH6YPJKpbuzCQYPkWtoZHDqWTzCSb4mJ3n0NRTsWy7Pm8LwG_Fd3pACl7JIY38IanPQDLoighFfo-Lriv5z3IdlhwbPnx0tk9sBwQBTRdZ8JkqqYkxUiB06phwr7mAnKEpQJ6HvhZBQ1cCnYZ_nIlrX9-I7qomrlE1UoQ\",\n"
|
||||
+ " \"d\": \"O9MJSOgcjjiVMNJ4jmBAh0mRHF_TlaVva70Imghtlgwxl8BLfcf1S8ueN1PD7xV6Cnq8YenSKsfiNOhC6yZ_fjW1syn5raWfj68eR7cjHWjLOvKjwVY33GBPNOvspNhVAFzeqfWneRTBbga53Agb6jjN0SUcZdJgnelzz5JNdOGaLzhacjH6YPJKpbuzCQYPkWtoZHDqWTzCSb4mJ3n0NRTsWy7Pm8LwG_Fd3pACl7JIY38IanPQDLoighFfo-Lriv5z3IdlhwbPnx0tk9sBwQBTRdZ8JkqqYkxUiB06phwr7mAnKEpQJ6HvhZBQ1cCnYZ_nIlrX9-I7qomrlE1UoQ\",\n"
|
||||
+ " \"e\": \"AQAB\",\n"
|
||||
+ " \"alg\": \"RS256\",\n"
|
||||
+ " \"n\": \"vCuB8MgwPZfziMSytEbBoOEwxsG7XI3MaVMoocziP4SjzU4IuWuE_DodbOHQwb_thUru57_Efe"
|
||||
+
|
||||
"--sfATHEa0Odv5ny3QbByqsvjyeHk6ZE4mSAV9BsHYa6GWAgEZtnDceeeDc0y76utXK2XHhC1Pysi2KG8KAzqDa099Yh7s31AyoueoMnrYTmWfEyDsQL_OAIiwgXakkS5U8QyXmWicCwXntDzkIMh8MjfPskesyli0XQD1AmCXVV3h2Opm1Amx0ggSOOiINUR5YRD6mKo49_cN-nrJWjtwSouqDdxHYP-4c7epuTcdS6kQHiQERBd1ejdpAxV4c0t0FHF7MOy9kw\"\n"
|
||||
+ " \"n\": \"vCuB8MgwPZfziMSytEbBoOEwxsG7XI3MaVMoocziP4SjzU4IuWuE_DodbOHQwb_thUru57_Efe--sfATHEa0Odv5ny3QbByqsvjyeHk6ZE4mSAV9BsHYa6GWAgEZtnDceeeDc0y76utXK2XHhC1Pysi2KG8KAzqDa099Yh7s31AyoueoMnrYTmWfEyDsQL_OAIiwgXakkS5U8QyXmWicCwXntDzkIMh8MjfPskesyli0XQD1AmCXVV3h2Opm1Amx0ggSOOiINUR5YRD6mKo49_cN-nrJWjtwSouqDdxHYP-4c7epuTcdS6kQHiQERBd1ejdpAxV4c0t0FHF7MOy9kw\"\n"
|
||||
+ "}";
|
||||
JwtClaims claims = new JwtClaims();
|
||||
claims.setGeneratedJwtId();
|
||||
claims.setIssuedAtToNow();
|
||||
//Expiration time must be set
|
||||
// Expiration time must be set
|
||||
NumericDate date = NumericDate.now();
|
||||
date.addSeconds(120*60);
|
||||
claims.setExpirationTime(date);
|
||||
claims.setNotBeforeMinutesInThePast(1);
|
||||
claims.setSubject("YOUR_SUBJECT");
|
||||
claims.setAudience("YOUR_AUDIENCE");
|
||||
//Add custom parameters, use String type for all values
|
||||
// Add custom parameters, all values should be String type
|
||||
claims.setClaim("userId", "1213234");
|
||||
claims.setClaim("email", "userEmail@youapp.com");
|
||||
JsonWebSignature jws = new JsonWebSignature();
|
||||
@@ -206,194 +339,9 @@ public class GenerateJwtDemo {
|
||||
jws.setKeyIdHeaderValue(keyId);
|
||||
jws.setPayload(claims.toJson());
|
||||
PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyJson)).getPrivateKey();
|
||||
|
||||
jws.setKey(privateKey);
|
||||
String jwtResult = jws.getCompactSerialization();
|
||||
System.out.println("Generate Json Web token , result is " + jwtResult);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Plugin Configuration Guide
|
||||
|
||||
## Configuration Fields
|
||||
|
||||
| Name | Data Type | Required | Default | Description |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `consumers` | array of object | Yes | - | Configures callers of the service for authenticating requests |
|
||||
| `_rules_` | array of object | Optional | - | Configures access control lists for specific routes or domains for authorizing requests |
|
||||
|
||||
The configuration field descriptions for each item in consumers are as follows:
|
||||
|
||||
| Name | Data Type | Required | Default | Description|
|
||||
| ----------------------- | ----------------- | -------- | ------------------------------------------------- | ------------------------ |
|
||||
| `name` | string | Yes | - | Configures the name of this consumer |
|
||||
| `jwks` | string | Yes | - | Specifies a JSON Web Key Set, as defined in https://www.rfc-editor.org/rfc/rfc7517, consisting of public keys (or symmetric keys) used to verify the signature of JWT |
|
||||
| `issuer` | string | Yes | - | The issuer of the JWT, which should be consistent with the iss field in the payload |
|
||||
| `claims_to_headers` | array of object | Optional | - | Extracts the specified fields from the JWT's payload and sets them to the specified request headers for forwarding to the backend |
|
||||
| `from_headers` | array of object | Optional | {"name":"Authorization","value_prefix":"Bearer "} | Extracts the JWT from the specified request headers |
|
||||
| `from_params` | array of string | Optional | access_token | Extracts the JWT from the specified URL parameters |
|
||||
| `from_cookies` | array of string | Optional | - | Extracts the JWT from the specified cookie(s) |
|
||||
| `clock_skew_seconds` | number | Optional | 60 | The amount of clock skew, in seconds, that is allowed when verifying the exp and iat fields of the JWT |
|
||||
| `keep_token` | bool | Optional | ture | Whether to keep the JWT when forwarding it to the backend |
|
||||
|
||||
**Note:**
|
||||
|
||||
- The default value is used only if neither `from_headers`, `from_params`, nor `from_cookies` are configured.
|
||||
|
||||
The configuration field descriptions for each item in `from_headers` are as follows:
|
||||
|
||||
| Name | Data Type | Required| Default | Description |
|
||||
| ---------------- | --------------- | ------- | ------ | --------------------------------------------------------- |
|
||||
| `name` | string | Yes | - | Specifies the request header to extract the JWT from |
|
||||
| `value_prefix` | string | Yes | - | Removes the specified prefix from the request header's value, leaving the rest as the JWT |
|
||||
|
||||
The configuration field descriptions for each item in `claims_to_headers` are as follows:
|
||||
|
||||
| Name | Data Type | Required| Default | Description |
|
||||
| ---------------- | --------------- | ------- | ------ | --------------------------------------------------------- |
|
||||
| `claim` | string | Yes | - | The name of the field in the JWT payload, which must be a string or unsigned integer |
|
||||
| `header` | string | Yes | - | Sets the value of the specified request header to the value of the specified field in the payload, for forwarding to the backend |
|
||||
| `override` | bool | Optional | true | If true, overrides an existing header with the same name; if false, appends the header to the existing headers |
|
||||
|
||||
The configuration field descriptions for each item in `_rules_` are as follows:
|
||||
|
||||
| Name | Data Type | Required| Default | Description |
|
||||
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | Optional, select either `_match_route_` or `_match_domain_` | - | Configures the route names to match|
|
||||
| `_match_domain_` | array of string | Optional, select either `_match_route_` or `_match_domain_` | - | Configures the domains to match |
|
||||
| `allow` | array of string | Required | - | Configures the consumer names allowed to access the matched requests |
|
||||
|
||||
**Note:**
|
||||
- If the `_rules_` field is not configured, authentication and authorization are enabled for all routes of the current gateway instance by default;
|
||||
- For authenticated and authorized requests, a `X-Mse-Consumer` field is added to the request header to identify the caller's name.
|
||||
|
||||
## Configuration Example
|
||||
|
||||
### Enable for Specific Routes or Domains
|
||||
|
||||
The following configuration enables Jwt Auth authentication and authorization for specific routes or domains of the gateway. If a JWT can match multiple `jwks`, the first matching `consumer` is hit according to the configuration order.
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- name: consumer1
|
||||
issuer: abcd
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"kid": "123",
|
||||
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
|
||||
"alg": "HS256"
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: consumer2
|
||||
issuer: abc
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "123",
|
||||
"alg": "RS256",
|
||||
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
|
||||
}
|
||||
]
|
||||
}
|
||||
# Use the _rules_ field for fine-grained rule configuration
|
||||
_rules_:
|
||||
# Rule 1: Effective when matched by route name
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer1
|
||||
# Rule 2: Effective when matched by domain name
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
In this example, the `route-a` and `route-b` specified in `_match_route_` are the names of the routes filled in when creating the gateway route. When these two routes are matched, access will be allowed for the caller with the `name` of `consumer1`, and other callers will not be allowed to access.
|
||||
|
||||
The `*.example.com` and `test.com` specified in `_match_domain_` are used to match the domain names of the requests. When a domain name match is found, access will be allowed for the caller with the `name` of `consumer2`, and other callers will not be allowed to access.
|
||||
|
||||
#### According to this configuration, the following requests are allowed:
|
||||
|
||||
Assuming the following requests will match the route-a route:
|
||||
|
||||
**JWT is set in URL parameter**
|
||||
```bash
|
||||
curl 'http://xxx.hello.com/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
**JWT is set in HTTP request header**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
After authentication and authorization, a `X-Mse-Consumer` field will be added in the request header with a value of `consumer1` in this example, to identify the name of the caller.
|
||||
|
||||
#### The following requests will be denied:
|
||||
|
||||
**Request without JWT provided, returns 401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test
|
||||
```
|
||||
|
||||
**The consumer matched by the provided JWT in the request does not have access, returns 403**
|
||||
```bash
|
||||
# consumer1 is not in the allow list of *.example.com
|
||||
curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
|
||||
```
|
||||
|
||||
### Enabling at Gateway Instance Level
|
||||
|
||||
The following configuration does not specify the `_rules_` field, so JWT authentication will be enabled at the gateway instance level:
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- name: consumer1
|
||||
issuer: abcd
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"kid": "123",
|
||||
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
|
||||
"alg": "HS256"
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: consumer2
|
||||
issuer: abc
|
||||
jwks: |
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "123",
|
||||
"alg": "RS256",
|
||||
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# Common Error Codes
|
||||
|
||||
| HTTP Status Code | Error Message | Reason Description|
|
||||
|------------------| ---------------------- | -------------------------------------------------------------------------------- |
|
||||
| 401 | JWT missing | The JWT is not provided in the request header. |
|
||||
| 401 | JWT expired | The JWT has expired. |
|
||||
| 401 | JWT verification fails | The JWT payload verification failed, such as the iss mismatch. |
|
||||
| 403 | Access denied | Access to the current route is denied. |
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
# 功能说明
|
||||
---
|
||||
title: Key 认证
|
||||
keywords: [higress,key auth]
|
||||
description: Key 认证插件配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
`key-auth`插件实现了基于 API Key 进行认证鉴权的功能,支持从 HTTP 请求的 URL 参数或者请求头解析 API Key,同时验证该 API Key 是否有权限访问。
|
||||
|
||||
# 配置字段
|
||||
## 运行属性
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
| `keys` | array of string | 必填 | - | API Key 的来源字段名称,可以是 URL 参数或者 HTTP 请求头名称 |
|
||||
| `in_query` | bool | `in_query` 和 `in_header` 至少有一个为 true | true | 配置 true 时,网关会尝试从 URL 参数中解析 API Key |
|
||||
| `in_header` | bool | `in_query` 和 `in_header` 至少有一个为 true | true | 配置 true 时,网关会尝试从 HTTP 请求头中解析 API Key |
|
||||
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
|
||||
插件执行阶段:`认证阶段`
|
||||
插件执行优先级:`310`
|
||||
|
||||
## 配置字段
|
||||
|
||||
**注意:**
|
||||
|
||||
- 在一个规则里,鉴权配置和认证配置不可同时存在
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
|
||||
### 认证配置
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `global_auth` | bool | 选填(**仅实例级别配置**) | - | 只能在实例级别配置,若配置为true,则全局生效认证机制; 若配置为false,则只对做了配置的域名和路由生效认证机制,若不配置则仅当没有域名和路由配置时全局生效(兼容老用户使用习惯)。 |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
| `keys` | array of string | 必填 | - | API Key 的来源字段名称,可以是 URL 参数或者 HTTP 请求头名称 |
|
||||
| `in_query` | bool | `in_query` 和 `in_header` 至少有一个为 true | true | 配置 true 时,网关会尝试从 URL 参数中解析 API Key |
|
||||
| `in_header` | bool | `in_query` 和 `in_header` 至少有一个为 true | true | 配置 true 时,网关会尝试从 HTTP 请求头中解析 API Key |
|
||||
|
||||
`consumers`中每一项的配置字段说明如下:
|
||||
|
||||
@@ -18,25 +35,22 @@
|
||||
| `credential` | string | 必填 | - | 配置该consumer的访问凭证 |
|
||||
| `name` | string | 必填 | - | 配置该consumer的名称 |
|
||||
|
||||
`_rules_` 中每一项的配置字段说明如下:
|
||||
### 鉴权配置(非必需)
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
|
||||
| `_match_domain_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的域名 |
|
||||
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求,配置允许访问的consumer名称 |
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `allow` | array of string | 选填(**非实例级别配置**) | - | 只能在路由或域名等细粒度规则上配置,对于符合匹配条件的请求,配置允许访问的 consumer,从而实现细粒度的权限控制 |
|
||||
|
||||
**注意:**
|
||||
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
## 配置示例
|
||||
|
||||
# 配置示例
|
||||
### 全局配置认证和路由粒度进行鉴权
|
||||
|
||||
## 对特定路由或域名开启
|
||||
以下配置将对网关特定路由或域名开启Key Auth认证和鉴权。credential字段不能重复。
|
||||
|
||||
以下配置将对网关特定路由或域名开启 Key Auth 认证和鉴权,注意`credential`字段不能重复
|
||||
在实例级别做如下插件配置:
|
||||
|
||||
```yaml
|
||||
global_auth: false
|
||||
consumers:
|
||||
- credential: 2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
name: consumer1
|
||||
@@ -44,31 +58,33 @@ consumers:
|
||||
name: consumer2
|
||||
keys:
|
||||
- apikey
|
||||
in_query: true
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer1
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer2
|
||||
- x-api-key
|
||||
```
|
||||
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name`为`consumer1`的调用者访问,其他调用者不允许访问;
|
||||
对 route-a 和 route-b 这两个路由做如下配置:
|
||||
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name`为`consumer2`的调用者访问,其他调用者不允许访问。
|
||||
```yaml
|
||||
allow:
|
||||
- consumer1
|
||||
```
|
||||
|
||||
### 根据该配置,下列请求可以允许访问:
|
||||
对 *.example.com 和 test.com 在这两个域名做如下配置:
|
||||
|
||||
```yaml
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
**说明:**
|
||||
|
||||
此例指定的route-a和route-b即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许name为consumer1的调用者访问,其他调用者不允许访问。
|
||||
|
||||
此例指定的*.example.com和test.com用于匹配请求的域名,当发现域名匹配时,将允许name为consumer2的调用者访问,其他调用者不被允许访问。
|
||||
|
||||
根据该配置,下列请求可以允许访问:
|
||||
|
||||
假设以下请求会匹配到route-a这条路由
|
||||
|
||||
n
|
||||
**将 API Key 设置在 url 参数中**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
@@ -80,7 +96,7 @@ curl http://xxx.hello.com/test -H 'x-api-key: 2bda943c-ba2b-11ec-ba07-00163e125
|
||||
|
||||
认证鉴权通过后,请求的header中会被添加一个`X-Mse-Consumer`字段,在此例中其值为`consumer1`,用以标识调用方的名称
|
||||
|
||||
### 下列请求将拒绝访问:
|
||||
下列请求将拒绝访问:
|
||||
|
||||
**请求未提供 API Key,返回401**
|
||||
```bash
|
||||
@@ -97,11 +113,12 @@ curl http://xxx.hello.com/test?apikey=926d90ac-ba2e-11ec-ab68-00163e1250b5
|
||||
curl http://xxx.hello.com/test?apikey=c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
|
||||
```
|
||||
|
||||
## 网关实例级别开启
|
||||
### 网关实例级别开启
|
||||
|
||||
以下配置未指定`_rules_`字段,因此将对网关实例级别开启 Key Auth 认证
|
||||
以下配置将对网关实例级别开启 Basic Auth 认证,所有请求均需要经过认证后才能访问。
|
||||
|
||||
```yaml
|
||||
global_auth: true
|
||||
consumers:
|
||||
- credential: 2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
name: consumer1
|
||||
@@ -109,13 +126,15 @@ consumers:
|
||||
name: consumer2
|
||||
keys:
|
||||
- apikey
|
||||
in_query: true
|
||||
- x-api-key
|
||||
```
|
||||
|
||||
# 相关错误码
|
||||
|
||||
## 相关错误码
|
||||
|
||||
| HTTP 状态码 | 出错信息 | 原因说明 |
|
||||
| ----------- | --------------------------------------------------------- | ----------------------- |
|
||||
| 401 | No API key found in request | 请求未提供 API Key |
|
||||
| 401 | Request denied by Key Auth check. Muti API key found in request | 请求提供多个 API Key |
|
||||
| 401 | Request denied by Key Auth check. No API key found in request | 请求未提供 API Key |
|
||||
| 401 | Request denied by Key Auth check. Invalid API key | 不允许当前 API Key 访问 |
|
||||
| 403 | Request denied by Basic Auth check. Unauthorized consumer | 请求的调用方无访问权限 |
|
||||
| 403 | Request denied by Key Auth check. Unauthorized consumer | 请求的调用方无访问权限 |
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
# Features
|
||||
The `key-auth` plug-in implements the authentication function based on the API Key, supports parsing the API Key from the URL parameter or request header of the HTTP request, and verifies whether the API Key has permission to access.
|
||||
---
|
||||
title: Key Authentication
|
||||
keywords: [higress,key auth]
|
||||
description: Key Authentication Plugin Configuration Reference
|
||||
---
|
||||
## Function Description
|
||||
The `key-auth` plugin implements authentication based on API Key, supporting the parsing of the API Key from HTTP request URL parameters or request headers, while also verifying whether the API Key has permission to access the resource.
|
||||
|
||||
# Configuration field
|
||||
## Runtime Properties
|
||||
Plugin Execution Phase: `Authentication Phase`
|
||||
Plugin Execution Priority: `310`
|
||||
|
||||
| Name | Data Type | Parameter requirements | Default| Description |
|
||||
| ----------- | --------------- | -------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------- |
|
||||
| `consumers` | array of object | Required | - | Configure the caller of the service to authenticate the request. |
|
||||
| `keys` | array of string | Required | - | The name of the source field of the API Key, which can be a URL parameter or an HTTP request header name. |
|
||||
| `in_query` | bool | At least one of `in_query` and `in_header` must be true. | true | When configured true, the gateway will try to parse the API Key from the URL parameters. |
|
||||
| `in_header` | bool | The same as above. | true | The same as above. |
|
||||
| `_rules_` | array of object | Optional | - | Configure the access list of a specific route or domain name for authenticating requests. |
|
||||
## Configuration Fields
|
||||
**Note:**
|
||||
- Authentication and authorization configurations cannot coexist within a single rule.
|
||||
- For requests that are authenticated, a header field `X-Mse-Consumer` will be added to identify the caller's name.
|
||||
|
||||
### Authentication Configuration
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ------------- | ---------------- | ----------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `global_auth` | bool | Optional (**Instance-Level Configuration Only**) | - | Can only be configured at the instance level; if set to true, the authentication mechanism takes effect globally; if set to false, it only applies to the configured hostnames and routes. If not configured, it will only take effect globally when no hostname and route configurations are present (to maintain compatibility with older user habits). |
|
||||
| `consumers` | array of object | Required | - | Configures the service callers for request authentication. |
|
||||
| `keys` | array of string | Required | - | Source field names for the API Key, which can be URL parameters or HTTP request header names. |
|
||||
| `in_query` | bool | At least one of `in_query` and `in_header` must be true | true | When configured as true, the gateway will attempt to parse the API Key from URL parameters. |
|
||||
| `in_header` | bool | At least one of `in_query` and `in_header` must be true | true | When configured as true, the gateway will attempt to parse the API Key from HTTP request headers. |
|
||||
|
||||
The configuration fields of each item in `consumers` are described as follows:
|
||||
The configuration field descriptions for each item in `consumers` are as follows:
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ------------ | --------- | ------------ | ------------- | ------------------------------ |
|
||||
| `credential` | string | Required | - | Configures the access credential for this consumer. |
|
||||
| `name` | string | Required | - | Configures the name for this consumer. |
|
||||
|
||||
| Name | Data Type | Parameter requirements | Default | Description |
|
||||
| ------------ | --------- | -----------------------| ------ | ------------------------------------------- |
|
||||
| `credential` | string | Required | - | Configure the consumer's access credentials. |
|
||||
| `name` | string | Required | - | Configure the name of the consumer. |
|
||||
### Authorization Configuration (Optional)
|
||||
| Name | Data Type | Requirements | Default Value | Description |
|
||||
| ----------- | ---------------- | ----------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `allow` | array of string | Optional (**Non-Instance Level Configuration**) | - | Can only be configured on fine-grained rules such as routes or hostnames; specifies the allowed consumers for matching requests, allowing for fine-grained permission control. |
|
||||
|
||||
The configuration fields of each item in `_rules_` are described as follows:
|
||||
|
||||
| Name | Data Type | Parameter requirements | Default| Description |
|
||||
| ---------------- | --------------- | --------------------------------------------------------------------- | ------ | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | Optional,Optionally fill in one of `_match_route_`, `_match_domain_`. | - | Configure the route name to match. |
|
||||
| `_match_domain_` | array of string | Optional,Optionally fill in one of `_match_route_`, `_match_domain_`. | - | Configure the domain name to match. |
|
||||
| `allow` | array of string | Required | - | For requests that meet the matching conditions, configure the name of the consumer that is allowed to access. |
|
||||
|
||||
**Warning:**
|
||||
- If the `_rules_` field is not configured, authentication will be enabled for all routes of the current gateway instance by default;
|
||||
- For a request that passes authentication, an `X-Mse-Consumer` field will be added to the request header to identify the name of the caller.
|
||||
|
||||
# Example configuration
|
||||
|
||||
## Enabled for specific routes or domains
|
||||
|
||||
The following configuration will enable Key Auth authentication and authentication for gateway-specific routes or domain names. Note that the `credential` field can not be repeated.
|
||||
## Configuration Example
|
||||
### Global Configuration for Authentication and Granular Route Authorization
|
||||
The following configuration will enable Key Auth authentication and authorization for specific routes or hostnames in the gateway. The `credential` field must not repeat.
|
||||
|
||||
At the instance level, do the following plugin configuration:
|
||||
```yaml
|
||||
global_auth: false
|
||||
consumers:
|
||||
- credential: 2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
name: consumer1
|
||||
@@ -45,64 +49,63 @@ consumers:
|
||||
name: consumer2
|
||||
keys:
|
||||
- apikey
|
||||
in_query: true
|
||||
# Use the _rules_ field for fine-grained rule configuration
|
||||
_rules_:
|
||||
# Rule 1: Match by route name to take effect
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer1
|
||||
# Rule 2: Take effect by domain name matching
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer2
|
||||
- x-api-key
|
||||
```
|
||||
|
||||
The `route-a` and `route-b` specified in `_match_route_` in this example are the route names filled in when creating the gateway route. When these two routes are matched, calls whose `name` is `consumer1` will be allowed Access by callers, other callers are not allowed to access;
|
||||
For routes route-a and route-b, do the following configuration:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer1
|
||||
```
|
||||
|
||||
`*.example.com` and `test.com` specified in `_match_domain_` in this example are used to match the domain name of the request. When the domain name matches, the caller whose `name` is `consumer2` will be allowed to access, and other calls access is not allowed.
|
||||
For the hostnames *.example.com and test.com, do the following configuration:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
### Depending on this configuration, the following requests would allow access:
|
||||
**Note:**
|
||||
The routes route-a and route-b specified in this example refer to the route names filled in when creating the gateway routes. When matched with these two routes, requests from the caller named consumer1 will be allowed while others will be denied.
|
||||
|
||||
Assume that the following request will match the route-a route:
|
||||
The specified hostnames *.example.com and test.com are used to match the request's domain name. When a domain name is matched, callers named consumer2 will be allowed while others will be denied.
|
||||
|
||||
**Set the API Key in the url parameter**
|
||||
Based on this configuration, the following requests will be allowed:
|
||||
|
||||
Assuming the following request matches route-a:
|
||||
**Setting API Key in URL Parameters**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
```
|
||||
**Set the API Key in the http request header**
|
||||
|
||||
**Setting API Key in HTTP Request Headers**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test -H 'x-api-key: 2bda943c-ba2b-11ec-ba07-00163e1250b5'
|
||||
```
|
||||
|
||||
After the authentication is passed, an `X-Mse-Consumer` field will be added to the header of the request. In this example, its value is `consumer1`, which is used to identify the name of the caller.
|
||||
After successful authentication and authorization, the request's header will have an added `X-Mse-Consumer` field with the value `consumer1`, to identify the name of the caller.
|
||||
|
||||
### The following requests will deny access:
|
||||
|
||||
**The request does not provide an API Key, return 401**
|
||||
The following requests will be denied access:
|
||||
**Request without an API Key returns 401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test
|
||||
```
|
||||
**The API Key provided by the request is not authorized to access, return 401**
|
||||
|
||||
**Request with an invalid API Key returns 401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test?apikey=926d90ac-ba2e-11ec-ab68-00163e1250b5
|
||||
```
|
||||
|
||||
**The caller matched according to the API Key provided in the request has no access rights, return 403**
|
||||
**Caller matched with provided API Key has no access rights, returns 403**
|
||||
```bash
|
||||
# consumer2 is not in the allow list of route-a
|
||||
curl http://xxx.hello.com/test?apikey=c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
|
||||
```
|
||||
|
||||
## Gateway instance level enabled
|
||||
|
||||
The following configuration does not specify the `_rules_` field, so Key Auth authentication will be enabled at the gateway instance level.
|
||||
### Enabling at the Instance Level
|
||||
The following configuration will enable Basic Auth authentication at the instance level for the gateway, requiring all requests to pass authentication before accessing.
|
||||
|
||||
```yaml
|
||||
global_auth: true
|
||||
consumers:
|
||||
- credential: 2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
name: consumer1
|
||||
@@ -110,14 +113,13 @@ consumers:
|
||||
name: consumer2
|
||||
keys:
|
||||
- apikey
|
||||
in_query: true
|
||||
- x-api-key
|
||||
```
|
||||
|
||||
# Error code
|
||||
|
||||
| HTTP status code | Error information | Reason |
|
||||
| ---------------- | --------------------------------------------------------- | -------------------------------------------- |
|
||||
| 401 | No API key found in request. | API not provided by request Key. |
|
||||
| 401 | Request denied by Key Auth check. Invalid API key. | Current API Key access is not allowed. |
|
||||
| 403 | Request denied by Basic Auth check. Unauthorized consumer. | The requested caller does not have access. |
|
||||
|
||||
## Related Error Codes
|
||||
| HTTP Status Code | Error Message | Reason Explanation |
|
||||
| ---------------- | ---------------------------------------------------------- | --------------------------------- |
|
||||
| 401 | Request denied by Key Auth check. Multiple API keys found in request | Multiple API Keys provided in the request. |
|
||||
| 401 | Request denied by Key Auth check. No API key found in request | API Key not provided in the request. |
|
||||
| 401 | Request denied by Key Auth check. Invalid API key | The current API Key is not authorized for access. |
|
||||
| 403 | Request denied by Key Auth check. Unauthorized consumer | The caller does not have access permissions. |
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
<p>
|
||||
<a href="README_EN.md"> English </a> | 中文
|
||||
</p>
|
||||
---
|
||||
title: 基于 Key 的本地限流
|
||||
keywords: [higress,key rate limit]
|
||||
description: Key 本地限流插件配置参考
|
||||
---
|
||||
|
||||
# 功能说明
|
||||
## 功能说明
|
||||
`key-rate-limit`插件实现了基于特定键值实现限流,键值来源可以是 URL 参数、HTTP 请求头
|
||||
|
||||
# 配置字段
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`默认阶段`
|
||||
插件执行优先级:`10`
|
||||
|
||||
## 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
@@ -23,9 +30,9 @@
|
||||
| query_per_hour | number | 选填,`query_per_second`,`query_per_minute`,`query_per_hour`,`query_per_day` 中选填一项 | - | 允许每小时请求次数 |
|
||||
| query_per_day | number | 选填,`query_per_second`,`query_per_minute`,`query_per_hour`,`query_per_day` 中选填一项 | - | 允许每天请求次数 |
|
||||
|
||||
# 配置示例
|
||||
## 配置示例
|
||||
|
||||
## 识别请求参数 apikey,进行区别限流
|
||||
### 识别请求参数 apikey,进行区别限流
|
||||
```yaml
|
||||
limit_by_param: apikey
|
||||
limit_keys:
|
||||
@@ -35,7 +42,7 @@ limit_keys:
|
||||
query_per_minute: 100
|
||||
```
|
||||
|
||||
## 识别请求头 x-ca-key,进行区别限流
|
||||
### 识别请求头 x-ca-key,进行区别限流
|
||||
```yaml
|
||||
limit_by_header: x-ca-key
|
||||
limit_keys:
|
||||
@@ -45,29 +52,3 @@ limit_keys:
|
||||
query_per_hour: 10
|
||||
|
||||
```
|
||||
|
||||
## 对特定路由或域名开启
|
||||
```yaml
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
limit_by_header: x-ca-key
|
||||
limit_keys:
|
||||
- key: 102234
|
||||
query_per_second: 10
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
limit_by_header: x-ca-key
|
||||
limit_keys:
|
||||
- key: 102234
|
||||
query_per_second: 100
|
||||
|
||||
```
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
|
||||
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
<p>
|
||||
English | <a href="README.md">中文</a>
|
||||
</p>
|
||||
---
|
||||
title: Key-based Local Rate Limiting
|
||||
keywords: [higress,key rate limit]
|
||||
description: Configuration reference for Key local rate limiting plugin
|
||||
---
|
||||
|
||||
# Description
|
||||
`key-rate-limit` plugin implements a rate-limiting function based on specific key-values. The key-values may come from URL parameters or HTTP headers.
|
||||
## Functional Description
|
||||
The `key-rate-limit` plugin implements rate limiting based on specific key values, which can originate from URL parameters or HTTP request headers.
|
||||
|
||||
# Configuration Fields
|
||||
## Running Properties
|
||||
Plugin execution phase: `default phase`
|
||||
Plugin execution priority: `10`
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| limit_by_header | string | Optional. Choose one from following: `limit_by_header`, `limit_by_param`. | - | The name of HTTP header used to obtain key-value used in rate-limiting. |
|
||||
| limit_by_param | string | Optional. Choose one from following: `limit_by_header`, `limit_by_param`. | - | The name of URL parameter used to obtain key-value used in rate-limiting. |
|
||||
| limit_keys | array of object | Required | - | Rate-limiting thresholds when matching specific key-values |
|
||||
## Configuration Fields
|
||||
|
||||
Field descriptions of `limit_keys` items:
|
||||
| Name | Data Type | Required | Default Value | Description |
|
||||
|-----------------|-----------------|---------------------------------------------------------------|---------------|----------------------------------------------------------------------------------------|
|
||||
| limit_by_header | string | Optional, choose one from `limit_by_header`, `limit_by_param` | - | Configuration for the source of the rate limiting key value (HTTP request header name) |
|
||||
| limit_by_param | string | Optional, choose one from `limit_by_header`, `limit_by_param` | - | Configuration for the source of the rate limiting key value (URL parameter name) |
|
||||
| limit_keys | array of object | Required | - | Configuration for the rate limiting frequency based on matched key values |
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| key | string | Required | - | Value to match of the specific key |
|
||||
| query_per_second | number | Optional. Choose one from following: `query_per_second`, `query_per_minute`, `query_per_hour`, `query_per_day`. | - | Number of requests allowed per second |
|
||||
| query_per_minute | number | Optional. Choose one from following: `query_per_second`, `query_per_minute`, `query_per_hour`, `query_per_day`. | - | Number of requests allowed per minute |
|
||||
| query_per_hour | number | Optional. Choose one from following: `query_per_second`, `query_per_minute`, `query_per_hour`, `query_per_day`. | - | Number of requests allowed per hour |
|
||||
| query_per_day | number | Optional. Choose one from following: `query_per_second`, `query_per_minute`, `query_per_hour`, `query_per_day`. | - | Number of requests allowed per day |
|
||||
Explanation of each configuration field in `limit_keys`
|
||||
|
||||
# Configuration Samples
|
||||
| Name | Data Type | Required | Default Value | Description |
|
||||
|------------------|-----------|-----------------------------------------------------------------------------------------------------|---------------|---------------------------------------|
|
||||
| key | string | Required | - | Matched key value |
|
||||
| query_per_second | number | Optional, choose one from `query_per_second`, `query_per_minute`, `query_per_hour`, `query_per_day` | - | Allowed number of requests per second |
|
||||
| query_per_minute | number | Optional, choose one from `query_per_second`, `query_per_minute`, `query_per_hour`, `query_per_day` | - | Allowed number of requests per minute |
|
||||
| query_per_hour | number | Optional, choose one from `query_per_second`, `query_per_minute`, `query_per_hour`, `query_per_day` | - | Allowed number of requests per hour |
|
||||
| query_per_day | number | Optional, choose one from `query_per_second`, `query_per_minute`, `query_per_hour`, `query_per_day` | - | Allowed number of requests per day |
|
||||
|
||||
## Use query parameter `apikey` for rate-limiting
|
||||
## Configuration Examples
|
||||
### Identify request parameter apikey for differentiated rate limiting
|
||||
```yaml
|
||||
limit_by_param: apikey
|
||||
limit_keys:
|
||||
@@ -35,7 +40,7 @@ limit_keys:
|
||||
query_per_minute: 100
|
||||
```
|
||||
|
||||
## Use HTTP header parameter `x-ca-key` for rate-limiting
|
||||
### Identify request header x-ca-key for differentiated rate limiting
|
||||
```yaml
|
||||
limit_by_header: x-ca-key
|
||||
limit_keys:
|
||||
@@ -44,28 +49,3 @@ limit_keys:
|
||||
- key: 308239
|
||||
query_per_hour: 10
|
||||
```
|
||||
|
||||
## Enable rate-limiting for specific routes or domains
|
||||
```yaml
|
||||
# Use _rules_ field for fine-grained rule configurations
|
||||
_rules_:
|
||||
# Rule 1: Match by route name
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
limit_by_header: x-ca-key
|
||||
limit_keys:
|
||||
- key: 102234
|
||||
query_per_second: 10
|
||||
# Rule 2: Match by domain
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
limit_by_header: x-ca-key
|
||||
limit_keys:
|
||||
- key: 102234
|
||||
query_per_second: 100
|
||||
```
|
||||
In the rule sample of `_match_route_`, `route-a` and `route-b` are the route names provided when creating a new gateway route. When the current route names matches the configuration, the rule following shall be applied.
|
||||
In the rule sample of `_match_domain_`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied.
|
||||
All rules shall be checked following the order of items in the `_rules_` field, The first matched rule will be applied. All remained will be ignored.
|
||||
@@ -1,21 +1,32 @@
|
||||
# 功能说明
|
||||
---
|
||||
title: OAuth2 认证
|
||||
keywords: [higress,oauth2]
|
||||
description: OAuth2 认证插件配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
`OAuth2`插件实现了基于JWT(JSON Web Tokens)进行OAuth2 Access Token签发的能力, 遵循[RFC9068](https://datatracker.ietf.org/doc/html/rfc9068)规范
|
||||
|
||||
# 插件配置说明
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`认证阶段`
|
||||
插件执行优先级:`350`
|
||||
|
||||
## 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
|
||||
| `issuer` | string | 选填 | Higress-Gateway | 用于填充JWT中的issuer |
|
||||
| `auth_path` | string | 选填 | /oauth2/token | 指定路径后缀用于签发Token,路由级配置时,要确保首先能匹配对应的路由 |
|
||||
| `global_credentials` | bool | 选填 | ture | 是否开启全局凭证,即允许路由A下的auth_path签发的Token可以用于访问路由B |
|
||||
| `auth_header_name` | string | 选填 | Authorization | 用于指定从哪个请求头获取JWT |
|
||||
| `token_ttl` | number | 选填 | 7200 | token从签发后多久内有效,单位为秒 |
|
||||
| `clock_skew_seconds` | number | 选填 | 60 | 校验JWT的exp和iat字段时允许的时钟偏移量,单位为秒 |
|
||||
| `keep_token` | bool | 选填 | ture | 转发给后端时是否保留JWT |
|
||||
### 认证配置
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
|
||||
| `issuer` | string | 选填 | Higress-Gateway | 用于填充JWT中的issuer |
|
||||
| `auth_path` | string | 选填 | /oauth2/token | 指定路径后缀用于签发Token,路由级配置时,要确保首先能匹配对应的路由, 使用 API 管理时,需要创建相同路径的接口 |
|
||||
| `global_credentials` | bool | 选填 | ture | 在通过 consumer 认证的前提下,允许任意路由签发的凭证访问 |
|
||||
| `auth_header_name` | string | 选填 | Authorization | 用于指定从哪个请求头获取JWT |
|
||||
| `token_ttl` | number | 选填 | 7200 | token从签发后多久内有效,单位为秒 |
|
||||
| `clock_skew_seconds` | number | 选填 | 60 | 校验JWT的exp和iat字段时允许的时钟偏移量,单位为秒 |
|
||||
| `keep_token` | bool | 选填 | ture | 转发给后端时是否保留JWT |
|
||||
| `global_auth` | array of string | 选填(**仅实例级别配置**) | - | 只能在实例级别配置,若配置为true,则全局生效认证机制; 若配置为false,则只对做了配置的域名和路由生效认证机制; 若不配置则仅当没有域名和路由配置时全局 生效(兼容老用户使用习惯) |
|
||||
|
||||
`consumers`中每一项的配置字段说明如下:
|
||||
|
||||
@@ -25,27 +36,53 @@
|
||||
| `client_id` | string | 必填 | - | OAuth2 client id |
|
||||
| `client_secret` | string | 必填 | - | OAuth2 client secret |
|
||||
|
||||
`_rules_` 中每一项的配置字段说明如下:
|
||||
**注意:**
|
||||
- 对于开启该配置的路由,如果路径后缀和`auth_path`匹配,则该路由不会到原目标服务,而是用于生成Token
|
||||
- 如果关闭`global_credentials`,请确保启用此插件的路由不是精确匹配路由,此时若存在另一条前缀匹配路由,则可能导致预期外行为
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
|
||||
| `_match_domain_` | array of string | 选填,`_match_route_`,`_match_domain_`中选填一项 | - | 配置要匹配的域名 |
|
||||
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求,配置允许访问的consumer名称 |
|
||||
### 鉴权配置(非必需)
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `allow` | array of string | 选填(**非实例级别配置**) | - | 只能在路由或域名等细粒度规则上配置,对于符合匹配条件的请求,配置允许访问的 consumer,从而实现细粒度的权限控制 |
|
||||
|
||||
**注意:**
|
||||
- 对于开启该配置的路由,如果路径后缀和`auth_path`匹配,则该路由到原目标服务,而是用于生成Token
|
||||
- 如果关闭`global_credentials`,请确保启用此插件的路由不是精确匹配路由,此时若存在另一条前缀匹配路由,则可能导致预期外行为
|
||||
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
|
||||
- 对于通过认证鉴权的请求,请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
|
||||
- 在一个规则里,鉴权配置和认证配置不可同时存在
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 对特定路由或域名开启
|
||||
### 路由粒度配置认证
|
||||
|
||||
在`route-a`和`route-b`两个路由做如下插件配置:
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
此时虽然使用同一份配置,但`route-a` 下签发的凭证无法用于访问 `route-b`,反之亦然。
|
||||
|
||||
如果希望同一份配置共享凭证访问权限,可以做如下配置:
|
||||
|
||||
```yaml
|
||||
global_credentials: true
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
### 全局配置认证,路由粒度进行鉴权
|
||||
|
||||
以下配置将对网关特定路由或域名开启 Jwt Auth 认证和鉴权,注意如果一个JWT能匹配多个`jwks`,则按照配置顺序命中第一个匹配的`consumer`
|
||||
|
||||
在实例级别做如下插件配置:
|
||||
|
||||
```yaml
|
||||
global_auth: false
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
@@ -53,37 +90,54 @@ consumers:
|
||||
- name: consumer2
|
||||
client_id: 87654321-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: hgfedcba-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer1
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name`为`consumer1`的调用者访问,其他调用者不允许访问;
|
||||
在`route-a`和`route-b`两个路由做如下插件配置:
|
||||
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name`为`consumer2`的调用者访问,其他调用者不允许访问。
|
||||
```yaml
|
||||
allow:
|
||||
- consumer1
|
||||
```
|
||||
|
||||
#### 使用 Client Credential 授权模式
|
||||
在`*.exmaple.com`和`test.com`两个域名做如下插件配置:
|
||||
|
||||
**获取 AccessToken**
|
||||
```yaml
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
此例指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name`为`consumer1`的调用者访问,其他调用者不允许访问;
|
||||
|
||||
此例指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name`为`consumer2`的调用者访问,其他调用者不允许访问。
|
||||
|
||||
### 网关实例级别开启
|
||||
|
||||
以下配置将对网关实例级别开启 OAuth2 认证,所有请求均需要经过认证后才能访问
|
||||
|
||||
```yaml
|
||||
global_auth: true
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
- name: consumer2
|
||||
client_id: 87654321-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: hgfedcba-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
# 请求示例
|
||||
|
||||
## 使用 Client Credential 授权模式
|
||||
|
||||
### 获取 AccessToken
|
||||
|
||||
```bash
|
||||
|
||||
# 通过 GET 方法获取
|
||||
# 通过 GET 方法获取(推荐)
|
||||
|
||||
curl 'http://test.com/oauth2/token?grant_type=client_credentials&client_id=12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx&client_secret=abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
|
||||
|
||||
# 通过 POST 方法获取 (需要先匹配到有真实目标服务的路由)
|
||||
# 通过 POST 方法获取(需要先匹配到有真实目标服务的路由,否则网关不会读取请求 Body)
|
||||
|
||||
curl 'http://test.com/oauth2/token' -H 'content-type: application/x-www-form-urlencoded' -d 'grant_type=client_credentials&client_id=12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx&client_secret=abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
|
||||
|
||||
@@ -96,29 +150,13 @@ curl 'http://test.com/oauth2/token' -H 'content-type: application/x-www-form-url
|
||||
|
||||
```
|
||||
|
||||
**使用 AccessToken 请求**
|
||||
### 使用 AccessToken 请求
|
||||
|
||||
```bash
|
||||
|
||||
curl 'http://test.com' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6ImFwcGxpY2F0aW9uXC9hdCtqd3QifQ.eyJhdWQiOiJkZWZhdWx0IiwiY2xpZW50X2lkIjoiMTIzNDU2NzgteHh4eC14eHh4LXh4eHgteHh4eHh4eHh4eHh4IiwiZXhwIjoxNjg3OTUxNDYzLCJpYXQiOjE2ODc5NDQyNjMsImlzcyI6IkhpZ3Jlc3MtR2F0ZXdheSIsImp0aSI6IjEwOTU5ZDFiLThkNjEtNGRlYy1iZWE3LTk0ODEwMzc1YjYzYyIsInN1YiI6ImNvbnN1bWVyMSJ9.NkT_rG3DcV9543vBQgneVqoGfIhVeOuUBwLJJ4Wycb0'
|
||||
|
||||
```
|
||||
因为 test.com 仅授权了 consumer2,但这个 Access Token 是基于 consumer1 的 `client_id`,`client_secret` 获取的,因此将返回 `403 Access Denied`
|
||||
|
||||
|
||||
### 网关实例级别开启
|
||||
|
||||
以下配置未指定`_rules_`字段,因此将对网关实例级别开启 OAuth2 认证
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
- name: consumer2
|
||||
client_id: 87654321-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: hgfedcba-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
# 常见错误码说明
|
||||
|
||||
@@ -126,4 +164,3 @@ consumers:
|
||||
| ----------- | ---------------------- | -------------------------------------------------------------------------------- |
|
||||
| 401 | Invalid Jwt token | 请求头未提供JWT, 或者JWT格式错误,或过期等原因 |
|
||||
| 403 | Access Denied | 无权限访问当前路由 |
|
||||
|
||||
|
||||
138
plugins/wasm-cpp/extensions/oauth/README_EN.md
Normal file
138
plugins/wasm-cpp/extensions/oauth/README_EN.md
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
title: OAuth2 Authentication
|
||||
keywords: [higress,oauth2]
|
||||
description: OAuth2 authentication plugin configuration reference
|
||||
---
|
||||
## Function Description
|
||||
`OAuth2` plugin implements the capability of issuing OAuth2 Access Tokens based on JWT (JSON Web Tokens), complying with the [RFC9068](https://datatracker.ietf.org/doc/html/rfc9068) specification.
|
||||
|
||||
## Runtime Properties
|
||||
Plugin execution phase: `Authentication Phase`
|
||||
Plugin execution priority: `350`
|
||||
|
||||
## Configuration Fields
|
||||
### Authentication Configuration
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
| -------------------- | ---------------- | ------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `consumers` | array of object | Required | - | Configures the callers of the service for request authentication |
|
||||
| `issuer` | string | Optional | Higress-Gateway | Used to fill the issuer in the JWT |
|
||||
| `auth_path` | string | Optional | /oauth2/token | Specifies the path suffix for issuing Tokens. When configured at the routing level, ensure it matches the corresponding route first. When using API management, create an interface with the same path. |
|
||||
| `global_credentials` | bool | Optional | true | Allows any route to issue credentials for access under the condition of authentication through the consumer. |
|
||||
| `auth_header_name` | string | Optional | Authorization | Specifies which request header to retrieve the JWT from |
|
||||
| `token_ttl` | number | Optional | 7200 | The time duration in seconds for which the token is valid after issuance. |
|
||||
| `clock_skew_seconds` | number | Optional | 60 | Allowed clock skew when verifying the exp and iat fields of the JWT, in seconds. |
|
||||
| `keep_token` | bool | Optional | true | Indicates whether to keep the JWT when forwarding to the backend. |
|
||||
| `global_auth` | array of string | Optional (**Instance-level configuration only**) | - | Can only be configured at the instance level. If set to true, the global authentication mechanism takes effect; if false, the authentication mechanism only takes effect for configured domains and routes; if not configured, global effect occurs only when there are no domain and route configurations (compatible with legacy user habits). |
|
||||
|
||||
The configuration fields for each item in `consumers` are as follows:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
| ----------------------- | ------------------| ----------- | ------------------------------------------------- | ---------------------------------- |
|
||||
| `name` | string | Required | - | Configures the name of the consumer. |
|
||||
| `client_id` | string | Required | - | OAuth2 client id |
|
||||
| `client_secret` | string | Required | - | OAuth2 client secret |
|
||||
|
||||
**Note:**
|
||||
- For routes with this configuration enabled, if the path suffix matches `auth_path`, the route will not forward to the original target service but will be used to generate a Token.
|
||||
- If `global_credentials` is disabled, please ensure that the routes enabling this plugin do not precisely match routes. If there is another prefix-matching route, it may lead to unexpected behavior.
|
||||
- For requests authenticated and authorized, the request header will have an `X-Mse-Consumer` field added to identify the caller's name.
|
||||
|
||||
### Authorization Configuration (Optional)
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
| ----------- | ---------------- | ---------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `allow` | array of string | Optional (**Non-instance-level configuration**) | - | Can only be configured on fine-grained rules such as routes or domains, allowing specified consumers to access requests that meet the matching conditions for fine-grained permission control. |
|
||||
|
||||
**Note:**
|
||||
- Authentication and authorization configurations cannot coexist in one rule.
|
||||
|
||||
## Configuration Example
|
||||
### Route Granularity Configuration Authentication
|
||||
For the two routes `route-a` and `route-b`, do the following plugin configuration:
|
||||
```yaml
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
At this time, although using the same configuration, the credentials issued under `route-a` cannot be used to access `route-b`, and vice versa.
|
||||
|
||||
If you want the same configuration to share credential access permissions, you can configure as follows:
|
||||
```yaml
|
||||
global_credentials: true
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
### Global Configuration Authentication, Route Granularity Authorization
|
||||
The following configuration will enable Jwt Auth for specific routes or domains on the gateway. Note that if a JWT matches multiple `jwks`, it will hit the first matching `consumer` in the order of configuration.
|
||||
|
||||
At the instance level, do the following plugin configuration:
|
||||
```yaml
|
||||
global_auth: false
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
- name: consumer2
|
||||
client_id: 87654321-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: hgfedcba-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
For the routes `route-a` and `route-b`, do the following plugin configuration:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer1
|
||||
```
|
||||
|
||||
For the domains `*.example.com` and `test.com`, do the following plugin configuration:
|
||||
```yaml
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
In this example, route names `route-a` and `route-b` refer to the route names filled in when creating the gateway route. When these two routes are matched, it will allow access for the caller with `name` as `consumer1`, and other callers will not be allowed to access.
|
||||
|
||||
In this example, the domains `*.example.com` and `test.com` are used to match request domains. When a matching domain is found, it will allow access for the caller with `name` as `consumer2`, while other callers will not be allowed to access.
|
||||
|
||||
### Enable at Gateway Instance Level
|
||||
The following configuration will enable OAuth2 authentication at the gateway instance level, requiring all requests to be authenticated before access.
|
||||
```yaml
|
||||
global_auth: true
|
||||
consumers:
|
||||
- name: consumer1
|
||||
client_id: 12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
- name: consumer2
|
||||
client_id: 87654321-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
client_secret: hgfedcba-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
# Request Example
|
||||
## Using Client Credential Authorization Mode
|
||||
### Get AccessToken
|
||||
```bash
|
||||
# Get via GET method (recommended)
|
||||
curl 'http://test.com/oauth2/token?grant_type=client_credentials&client_id=12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx&client_secret=abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
|
||||
|
||||
# Get via POST method (requires matching a route with a real target service first, or the gateway will not read the request Body)
|
||||
curl 'http://test.com/oauth2/token' -H 'content-type: application/x-www-form-urlencoded' -d 'grant_type=client_credentials&client_id=12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx&client_secret=abcdefgh-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
|
||||
|
||||
# Simply get the access_token field from the response:
|
||||
{
|
||||
"token_type": "bearer",
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6ImFwcGxpY2F0aW9uXC9hdCtqd3QifQ.eyJhdWQiOiJkZWZhdWx0IiwiY2xpZW50X2lkIjoiMTIzNDU2NzgteHh4eC14eHh4LXh4eHgteHh4eHh4eHh4eHh4IiwiZXhwIjoxNjg3OTUxNDYzLCJpYXQiOjE2ODc5NDQyNjMsImlzcyI6IkhpZ3Jlc3MtR2F0ZXdheSIsImp0aSI6IjEwOTU5ZDFiLThkNjEtNGRlYy1iZWE3LTk0ODEwMzc1YjYzYyIsInN1YiI6ImNvbnN1bWVyMSJ9.NkT_rG3DcV9543vBQgneVqoGfIhVeOuUBwLJJ4Wycb0",
|
||||
"expires_in": 7200
|
||||
}
|
||||
```
|
||||
|
||||
### AccessToken Request
|
||||
```bash
|
||||
curl 'http://test.com' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6ImFwcGxpY2F0aW9uXC9hdCtqd3QifQ.eyJhdWQiOiJkZWZhdWx0IiwiY2xpZW50X2lkIjoiMTIzNDU2NzgteHh4eC14eHh4LXh4eHgteHh4eHh4eHh4eHh4IiwiZXhwIjoxNjg3OTUxNDYzLCJpYXQiOjE2ODc5NDQyNjMsImlzcyI6IkhpZ3Jlc3MtR2F0ZXdheSIsImp0aSI6IjEwOTU5ZDFiLThkNjEtNGRlYy1iZWE3LTk0ODEwMzc1YjYzYyIsInN1YiI6ImNvbnN1bWVyMSJ9.NkT_rG3DcV9543vBQgneVqoGfIhVeOuUBwLJJ4Wycb0'
|
||||
```
|
||||
|
||||
# Common Error Code Description
|
||||
| HTTP Status Code | Error Message | Explanation |
|
||||
| ---------------- | ----------------------| --------------------------------------------------------------------------- |
|
||||
| 401 | Invalid Jwt token | JWT not provided in request header, or JWT format is incorrect, or expired, etc. |
|
||||
| 403 | Access Denied | No permission to access the current route. |
|
||||
@@ -1,11 +1,18 @@
|
||||
<p>
|
||||
<a href="README_EN.md"> English </a> | 中文
|
||||
</p>
|
||||
---
|
||||
title: 请求屏蔽
|
||||
keywords: [higress,request block]
|
||||
description: 请求屏蔽插件配置参考
|
||||
---
|
||||
|
||||
# 功能说明
|
||||
## 功能说明
|
||||
`request-block`插件实现了基于 URL、请求头等特征屏蔽 HTTP 请求,可以用于防护部分站点资源不对外部暴露
|
||||
|
||||
# 配置字段
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`鉴权阶段`
|
||||
插件执行优先级:`320`
|
||||
|
||||
## 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
@@ -16,9 +23,9 @@
|
||||
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
|
||||
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
|
||||
|
||||
# 配置示例
|
||||
## 配置示例
|
||||
|
||||
## 屏蔽请求 url 路径
|
||||
### 屏蔽请求 url 路径
|
||||
```yaml
|
||||
block_urls:
|
||||
- swagger.html
|
||||
@@ -33,7 +40,7 @@ curl http://example.com?foo=Bar
|
||||
curl http://exmaple.com/Swagger.html
|
||||
```
|
||||
|
||||
## 屏蔽请求 header
|
||||
### 屏蔽请求 header
|
||||
```yaml
|
||||
block_headers:
|
||||
- example-key
|
||||
@@ -47,7 +54,7 @@ curl http://example.com -H 'example-key: 123'
|
||||
curl http://exmaple.com -H 'my-header: example-value'
|
||||
```
|
||||
|
||||
## 屏蔽请求 body
|
||||
### 屏蔽请求 body
|
||||
```yaml
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
@@ -61,30 +68,8 @@ curl http://example.com -d 'Hello World'
|
||||
curl http://exmaple.com -d 'hello world'
|
||||
```
|
||||
|
||||
## 对特定路由或域名开启
|
||||
```yaml
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
```
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
|
||||
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
|
||||
|
||||
# 请求 Body 大小限制
|
||||
## 请求 Body 大小限制
|
||||
|
||||
当配置了 `block_bodies` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作
|
||||
当配置了 `block_bodies` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
<p>
|
||||
English | <a href="README.md">中文</a>
|
||||
</p>
|
||||
---
|
||||
title: Request Blocking
|
||||
keywords: [higress,request block]
|
||||
description: Request blocking plugin configuration reference
|
||||
---
|
||||
## Function Description
|
||||
The `request-block` plugin implements HTTP request blocking based on features such as URL, request headers, etc. It can be used to protect certain site resources from being exposed to the outside.
|
||||
|
||||
# Description
|
||||
`request-block` plugin implements a request blocking function based on request characteristics such as URL and request header. It can be used to protect internal resources from unauthorized access.
|
||||
## Running Attributes
|
||||
Plugin Execution Stage: `Authentication Stage`
|
||||
|
||||
# Configuration Fields
|
||||
Plugin Execution Priority: `320`
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| block_urls | array of string | Optional. Choose one from following: `block_urls`, `block_headers`, `block_bodies` | - | HTTP URLs to be blocked. |
|
||||
| block_headers | array of string | Optional. Choose one from following: `block_urls`, `block_headers`, `block_bodies` | - | HTTP request headers to be blocked. |
|
||||
| block_bodies | array of string | Optional. Choose one from following: `block_urls` ,`block_headers`, `block_bodies` | - | HTTP request bodies to be blocked. |
|
||||
| blocked_code | number | Optional | 403 | HTTP response status code to be sent when corresponding request is blocked. |
|
||||
| blocked_message | string | Optional | - | HTTP response body to be sent when corresponding request is blocked. |
|
||||
| case_sensitive | bool | Optional | true | Whether to use case-senstive comparison when matching. Enabled by default. |
|
||||
## Configuration Fields
|
||||
| Name | Data Type | Fill Requirement | Default Value | Description |
|
||||
|--------------------|--------------------|---------------------------------------------------------|---------------|------------------------------------------------------------|
|
||||
| block_urls | array of string | Optional, at least one of `block_urls`, `block_headers`, `block_bodies` must be filled | - | Configure strings for matching URLs that need to be blocked |
|
||||
| block_headers | array of string | Optional, at least one of `block_urls`, `block_headers`, `block_bodies` must be filled | - | Configure strings for matching request headers that need to be blocked |
|
||||
| block_bodies | array of string | Optional, at least one of `block_urls`, `block_headers`, `block_bodies` must be filled | - | Configure strings for matching request bodies that need to be blocked |
|
||||
| blocked_code | number | Optional | 403 | Configure the HTTP status code returned when a request is blocked |
|
||||
| blocked_message | string | Optional | - | Configure the HTTP response body returned when a request is blocked |
|
||||
| case_sensitive | bool | Optional | true | Configure whether matching is case-sensitive, default is case-sensitive |
|
||||
|
||||
# Configuration Samples
|
||||
|
||||
## Block Specific Request URLs
|
||||
## Configuration Example
|
||||
### Blocking Request URL Paths
|
||||
```yaml
|
||||
block_urls:
|
||||
- swagger.html
|
||||
@@ -26,65 +30,39 @@ block_urls:
|
||||
case_sensitive: false
|
||||
```
|
||||
|
||||
According to the configuration above, following requests will be blocked:
|
||||
|
||||
Based on this configuration, the following requests will be denied access:
|
||||
```bash
|
||||
curl http://example.com?foo=Bar
|
||||
curl http://exmaple.com/Swagger.html
|
||||
```
|
||||
|
||||
## Block Specific Request Headers
|
||||
### Blocking Request Headers
|
||||
```yaml
|
||||
block_headers:
|
||||
- example-key
|
||||
- example-value
|
||||
```
|
||||
|
||||
According to the configuration above, following requests will be blocked:
|
||||
|
||||
Based on this configuration, the following requests will be denied access:
|
||||
```bash
|
||||
curl http://example.com -H 'example-key: 123'
|
||||
curl http://exmaple.com -H 'my-header: example-value'
|
||||
```
|
||||
|
||||
## Block Specific Request Bodies
|
||||
### Blocking Request Bodies
|
||||
```yaml
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
case_sensitive: false
|
||||
```
|
||||
|
||||
According to the configuration above, following requests will be blocked:
|
||||
|
||||
Based on this configuration, the following requests will be denied access:
|
||||
```bash
|
||||
curl http://example.com -d 'Hello World'
|
||||
curl http://exmaple.com -d 'hello world'
|
||||
```
|
||||
|
||||
## Only Enable for Specific Routes or Domains
|
||||
```yaml
|
||||
# Use _rules_ field for fine-grained rule configurations
|
||||
_rules_:
|
||||
# Rule 1: Match by route name
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
# Rule 2: Match by domain
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
```
|
||||
In the rule sample of `_match_route_`, `route-a` and `route-b` are the route names provided when creating a new gateway route. When the current route names matches the configuration, the rule following shall be applied.
|
||||
In the rule sample of `_match_domain_`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied.
|
||||
All rules shall be checked following the order of items in the `_rules_` field, The first matched rule will be applied. All remained will be ignored.
|
||||
## Request Body Size Limit
|
||||
When `block_bodies` is configured, only request bodies smaller than 32 MB are supported for matching. If the request body exceeds this limit and there are no matching `block_urls` or `block_headers`, the blocking operation will not be executed for that request.
|
||||
|
||||
# Maximum Request Body Size Limitation
|
||||
|
||||
When `block_bodies` is configured, body matching shall only be performed when its size is smaller than 32MB. If not, and no `block_urls` or `block_headers` configuration is matched, the request won't be blocked.
|
||||
When `block_bodies` is configured, if the size of request body exceeds the global configuration of DownstreamConnectionBufferLimits, a ``413 Payload Too Large`` response will be returned.
|
||||
When `block_bodies` is configured and the request body exceeds the global configuration DownstreamConnectionBufferLimits, it will return `413 Payload Too Large`.
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# 功能说明
|
||||
## 功能说明
|
||||
|
||||
**此插件已经废弃,从1.4版本开始,Higress 从根本上解决了这一问题,不再需要此插件**
|
||||
|
||||
`http2-misdirect`插件用于解决网关开启 HTTP2 时,因为浏览器复用连接导致访问出现 404 等问题。
|
||||
|
||||
# 插件原理
|
||||
## 插件原理
|
||||
|
||||
HTTP2 协议允许两个不同域名的请求,在域名解析到相同 IP,并且使用了相同证书的情况下,复用同一条连接。这在一些特殊场景会导致复用连接的请求发送给了错误的 Virtual Host 进行处理,从而导致出现 404 等问题。
|
||||
本插件基于`HTTP/2 RFC 7540`的`9.1.1`和`9.1.2`章节描述,在发现请求 SNI 与当前 Virtual Host 不匹配时,发送 HTTP 421 状态码,强制浏览器新建连接,并根据当前请求域名生成匹配的 SNI,从而让网关能正确处理路由。
|
||||
|
||||
# 浏览器兼容性
|
||||
## 浏览器兼容性
|
||||
|
||||
`Safari` 浏览器 `15.1` 版本以下不支持 HTTP 421 状态码,若有此类客户端访问场景,建议对相应域名关闭 HTTP2 的 ALPN
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.19-tinygo0.28.1-oras1.0.0
|
||||
ARG BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.20.14-tinygo0.29.0-oras1.0.0
|
||||
FROM $BUILDER as builder
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
PLUGIN_NAME ?= hello-world
|
||||
BUILDER_REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
|
||||
REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
|
||||
GO_VERSION ?= 1.19
|
||||
TINYGO_VERSION ?= 0.28.1
|
||||
GO_VERSION ?= 1.20.14
|
||||
TINYGO_VERSION ?= 0.29.0
|
||||
ORAS_VERSION ?= 1.0.0
|
||||
HIGRESS_VERSION ?= 1.0.0-rc
|
||||
USE_HIGRESS_TINYGO ?= false
|
||||
|
||||
@@ -5,10 +5,13 @@ description: AI Agent插件配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
一个可定制化的 API AI Agent,支持配置 http method 类型为 GET 与 POST 的 API,目前只支持非流式模式。
|
||||
一个可定制化的 API AI Agent,支持配置 http method 类型为 GET 与 POST 的 API,支持多轮对话,支持流式与非流式模式。
|
||||
agent流程图如下:
|
||||

|
||||

|
||||
|
||||
## 运行属性
|
||||
插件执行阶段:`默认阶段`
|
||||
插件执行优先级:`200`
|
||||
|
||||
## 配置字段
|
||||
|
||||
@@ -42,18 +45,19 @@ agent流程图如下:
|
||||
|
||||
`apiProvider`的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|-----------------|-----------|---------|--------|------------------------------------------|
|
||||
| `apiKey` | object | 非必填 | - | 用于在访问外部 API 服务时进行认证的令牌。 |
|
||||
| `serviceName` | string | 必填 | - | 访问外部 API 服务名 |
|
||||
| `servicePort` | int | 必填 | - | 访问外部 API 服务端口 |
|
||||
| `domain` | string | 必填 | - | 访访问外部 API 时域名 |
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|-------------------|-----------|---------|--------|------------------------------------------|
|
||||
| `apiKey` | object | 非必填 | - | 用于在访问外部 API 服务时进行认证的令牌。 |
|
||||
| `maxExecutionTime`| int | 非必填 | 50000 | 每一次请求API的超时时间,单位毫秒。 |
|
||||
| `serviceName` | string | 必填 | - | 访问外部 API 服务名 |
|
||||
| `servicePort` | int | 必填 | - | 访问外部 API 服务端口 |
|
||||
| `domain` | string | 必填 | - | 访访问外部 API 时域名 |
|
||||
|
||||
`apiKey`的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|-------------------|---------|------------|--------|-------------------------------------------------------------------------------|
|
||||
| `in` | string | 非必填 | header | 在访问外部 API 服务时进行认证的令牌是放在 header 中还是放在 query 中,默认是 header。
|
||||
|-------------------|---------|------------|--------|-----------------------------------------------------------------------------------------|
|
||||
| `in` | string | 非必填 | none | 在访问外部 API 服务时进行认证的令牌是放在 header 中还是放在 query 中,如果API没有令牌,填none。
|
||||
| `name` | string | 非必填 | - | 用于在访问外部 API 服务时进行认证的令牌的名称。 |
|
||||
| `value` | string | 非必填 | - | 用于在访问外部 API 服务时进行认证的令牌的值。 |
|
||||
|
||||
@@ -71,11 +75,8 @@ agent流程图如下:
|
||||
|-----------------|-----------|-----------|--------|---------------------------------------------|
|
||||
| `question` | string | 非必填 | - | Agent ReAct 模板的 question 部分 |
|
||||
| `thought1` | string | 非必填 | - | Agent ReAct 模板的 thought1 部分 |
|
||||
| `actionInput` | string | 非必填 | - | Agent ReAct 模板的 actionInput 部分 |
|
||||
| `observation` | string | 非必填 | - | Agent ReAct 模板的 observation 部分 |
|
||||
| `thought2` | string | 非必填 | - | Agent ReAct 模板的 thought2 部分 |
|
||||
| `finalAnswer` | string | 非必填 | - | Agent ReAct 模板的 finalAnswer 部分 |
|
||||
| `begin` | string | 非必填 | - | Agent ReAct 模板的 begin 部分 |
|
||||
|
||||
## 用法示例
|
||||
|
||||
@@ -321,6 +322,21 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
|
||||
|
||||
**请求示例**
|
||||
|
||||
```shell
|
||||
curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role": "user","content": "济南的天气如何?"},{ "role": "assistant","content": "目前,济南市的天气为多云,气温为24℃,数据更新时间为2024年9月12日21时50分14秒。"},{"role": "user","content": "北京呢?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content":"目前,北京市的天气为晴朗,气温为19℃,数据更新时间为2024年9月12日22时17分40秒。"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":999,"completion_tokens":76,"total_tokens":1075}}
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```shell
|
||||
curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
@@ -347,4 +363,4 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
|
||||
|
||||
```json
|
||||
{"id":"65dcf12c-61ff-9e68-bffa-44fc9e6070d5","choices":[{"index":0,"message":{"role":"assistant","content":" “九头蛇万岁!”的德语翻译为“Hoch lebe Hydra!”。"},"finish_reason":"stop"}],"created":1724043865,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":908,"completion_tokens":52,"total_tokens":960}}
|
||||
```
|
||||
```
|
||||
|
||||
337
plugins/wasm-go/extensions/ai-agent/README_EN.md
Normal file
337
plugins/wasm-go/extensions/ai-agent/README_EN.md
Normal file
@@ -0,0 +1,337 @@
|
||||
---
|
||||
title: AI Agent
|
||||
keywords: [ AI Gateway, AI Agent ]
|
||||
description: AI Agent plugin configuration reference
|
||||
---
|
||||
## Functional Description
|
||||
A customizable API AI Agent that supports configuring HTTP method types as GET and POST APIs. Supports multiple dialogue rounds, streaming and non-streaming modes.
|
||||
The agent flow chart is as follows:
|
||||

|
||||
|
||||
## Runtime Properties
|
||||
Plugin execution phase: `Default Phase`
|
||||
Plugin execution priority: `200`
|
||||
|
||||
## Configuration Fields
|
||||
|
||||
### Basic Configuration
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|------------------|-----------|-------------|---------------|----------------------------------|
|
||||
| `llm` | object | Required | - | Configuration information for AI service provider |
|
||||
| `apis` | object | Required | - | Configuration information for external API service provider |
|
||||
| `promptTemplate` | object | Optional | - | Configuration information for Agent ReAct template |
|
||||
|
||||
The configuration fields for `llm` are as follows:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|--------------------|-----------|-------------|---------------|-------------------------------------|
|
||||
| `apiKey` | string | Required | - | Token for authentication when accessing large model services. |
|
||||
| `serviceName` | string | Required | - | Name of the large model service |
|
||||
| `servicePort` | int | Required | - | Port of the large model service |
|
||||
| `domain` | string | Required | - | Domain for accessing the large model service |
|
||||
| `path` | string | Required | - | Path for accessing the large model service |
|
||||
| `model` | string | Required | - | Model name for accessing the large model service |
|
||||
| `maxIterations` | int | Required | 15 | Maximum steps before ending the execution loop |
|
||||
| `maxExecutionTime` | int | Required | 50000 | Timeout for each request to the large model, in milliseconds |
|
||||
| `maxTokens` | int | Required | 1000 | Token limit for each request to the large model |
|
||||
|
||||
The configuration fields for `apis` are as follows:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|-----------------|-----------|-------------|---------------|-------------------------------------|
|
||||
| `apiProvider` | object | Required | - | Information about the external API service |
|
||||
| `api` | string | Required | - | OpenAPI documentation of the tool |
|
||||
|
||||
The configuration fields for `apiProvider` are as follows:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|-------------------|-----------|-------------|---------------|--------------------------------------------------|
|
||||
| `apiKey` | object | Optional | - | Token for authentication when accessing external API services. |
|
||||
| `maxExecutionTime`| int | Optional | 50000 | Timeout for each request to the API, in milliseconds|
|
||||
| `serviceName` | string | Required | - | Name of the external API service |
|
||||
| `servicePort` | int | Required | - | Port of the external API service |
|
||||
| `domain` | string | Required | - | Domain for accessing the external API |
|
||||
|
||||
The configuration fields for `apiKey` are as follows:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|-------------------|-----------|-------------|---------------|-------------------------------------------------------------------------------------|
|
||||
| `in` | string | Optional | none | Whether the authentication token for accessing the external API service is in the header or in the query; If the API does not have a token, fill in none. |
|
||||
| `name` | string | Optional | - | The name of the token for authentication when accessing the external API service. |
|
||||
| `value` | string | Optional | - | The value of the token for authentication when accessing the external API service. |
|
||||
|
||||
The configuration fields for `promptTemplate` are as follows:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|-----------------|-----------|-------------|---------------|----------------------------------------------------|
|
||||
| `language` | string | Optional | EN | Language type of the Agent ReAct template, including CH and EN. |
|
||||
| `chTemplate` | object | Optional | - | Agent ReAct Chinese template |
|
||||
| `enTemplate` | object | Optional | - | Agent ReAct English template |
|
||||
|
||||
The configuration fields for `chTemplate` and `enTemplate` are as follows:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|-----------------|-----------|-------------|---------------|---------------------------------------------------|
|
||||
| `question` | string | Optional | - | The question part of the Agent ReAct template |
|
||||
| `thought1` | string | Optional | - | The thought1 part of the Agent ReAct template |
|
||||
| `observation` | string | Optional | - | The observation part of the Agent ReAct template |
|
||||
| `thought2` | string | Optional | - | The thought2 part of the Agent ReAct template |
|
||||
|
||||
## Usage Example
|
||||
**Configuration Information**
|
||||
```yaml
|
||||
llm:
|
||||
apiKey: xxxxxxxxxxxxxxxxxx
|
||||
domain: dashscope.aliyuncs.com
|
||||
serviceName: dashscope.dns
|
||||
servicePort: 443
|
||||
path: /compatible-mode/v1/chat/completions
|
||||
model: qwen-max-0403
|
||||
maxIterations: 2
|
||||
promptTemplate:
|
||||
language: CH
|
||||
apis:
|
||||
- apiProvider:
|
||||
domain: restapi.amap.com
|
||||
serviceName: geo.dns
|
||||
servicePort: 80
|
||||
apiKey:
|
||||
in: query
|
||||
name: key
|
||||
value: xxxxxxxxxxxxxxx
|
||||
api: |
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Amap
|
||||
description: Get related information of POI
|
||||
version: v1.0.0
|
||||
servers:
|
||||
- url: https://restapi.amap.com
|
||||
paths:
|
||||
/v5/place/text:
|
||||
get:
|
||||
description: Get latitude and longitude coordinates based on POI name
|
||||
operationId: get_location_coordinate
|
||||
parameters:
|
||||
- name: keywords
|
||||
in: query
|
||||
description: POI name, must be in Chinese
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: region
|
||||
in: query
|
||||
description: The name of the region where the POI is located, must be in Chinese
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
deprecated: false
|
||||
/v5/place/around:
|
||||
get:
|
||||
description: Search for POI near the given coordinates
|
||||
operationId: search_nearby_pois
|
||||
parameters:
|
||||
- name: keywords
|
||||
in: query
|
||||
description: Keywords for the target POI
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: location
|
||||
in: query
|
||||
description: Latitude and longitude of the center point, separated by a comma
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
deprecated: false
|
||||
components:
|
||||
schemas: {}
|
||||
- apiProvider:
|
||||
domain: api.seniverse.com
|
||||
serviceName: seniverse.dns
|
||||
servicePort: 80
|
||||
apiKey:
|
||||
in: query
|
||||
name: key
|
||||
value: xxxxxxxxxxxxxxx
|
||||
api: |
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: XZWeather
|
||||
description: Get weather related information
|
||||
version: v1.0.0
|
||||
servers:
|
||||
- url: https://api.seniverse.com
|
||||
paths:
|
||||
/v3/weather/now.json:
|
||||
get:
|
||||
description: Get weather conditions for a specified city
|
||||
operationId: get_weather_now
|
||||
parameters:
|
||||
- name: location
|
||||
in: query
|
||||
description: The city to query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: language
|
||||
in: query
|
||||
description: Language used for the weather query results
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
default: zh-Hans
|
||||
enum:
|
||||
- zh-Hans
|
||||
- en
|
||||
- ja
|
||||
- name: unit
|
||||
in: query
|
||||
description: Units of temperature, available in Celsius and Fahrenheit
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
default: c
|
||||
enum:
|
||||
- c
|
||||
- f
|
||||
deprecated: false
|
||||
components:
|
||||
schemas: {}
|
||||
- apiProvider:
|
||||
apiKey:
|
||||
in: "header"
|
||||
name: "DeepL-Auth-Key"
|
||||
value: "73xxxxxxxxxxxxxxx:fx"
|
||||
domain: "api-free.deepl.com"
|
||||
serviceName: "deepl.dns"
|
||||
servicePort: 443
|
||||
api: |
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: DeepL API Documentation
|
||||
description: The DeepL API provides programmatic access to DeepL’s machine translation technology.
|
||||
version: v1.0.0
|
||||
servers:
|
||||
- url: https://api-free.deepl.com/v2
|
||||
paths:
|
||||
/translate:
|
||||
post:
|
||||
summary: Request Translation
|
||||
operationId: translateText
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- text
|
||||
- target_lang
|
||||
properties:
|
||||
text:
|
||||
description: |
|
||||
Text to be translated. Only UTF-8-encoded plain text is supported.
|
||||
The parameter may be specified up to 50 times in a single request.
|
||||
Translations are returned in the same order as they are requested.
|
||||
type: array
|
||||
maxItems: 50
|
||||
items:
|
||||
type: string
|
||||
example: Hello, World!
|
||||
target_lang:
|
||||
description: The language into which the text should be translated.
|
||||
type: string
|
||||
enum:
|
||||
- BG
|
||||
- CS
|
||||
- DA
|
||||
- DE
|
||||
- EL
|
||||
- EN-GB
|
||||
- EN-US
|
||||
- ES
|
||||
- ET
|
||||
- FI
|
||||
- FR
|
||||
- HU
|
||||
- ID
|
||||
- IT
|
||||
- JA
|
||||
- KO
|
||||
- LT
|
||||
- LV
|
||||
- NB
|
||||
- NL
|
||||
- PL
|
||||
- PT-BR
|
||||
- PT-PT
|
||||
- RO
|
||||
- RU
|
||||
- SK
|
||||
- SL
|
||||
- SV
|
||||
- TR
|
||||
- UK
|
||||
- ZH
|
||||
- ZH-HANS
|
||||
example: DE
|
||||
components:
|
||||
schemas: {}
|
||||
```
|
||||
This example configures three services demonstrating both GET and POST types of tools. The GET type tools include Amap and XZWeather, while the POST type tool is the DeepL translation. All three services need to be properly configured in the Higress service with DNS domain names and should be healthy.
|
||||
Amap provides two tools, one for obtaining the coordinates of a specified location and the other for searching for points of interest near the coordinates. Document: https://lbs.amap.com/api/webservice/guide/api-advanced/newpoisearch
|
||||
XZWeather provides one tool to get real-time weather conditions for a specified city, supporting results in Chinese, English, and Japanese, as well as representations in Celsius and Fahrenheit. Document: https://seniverse.yuque.com/hyper_data/api_v3/nyiu3t
|
||||
DeepL provides one tool for translating given sentences, supporting multiple languages. Document: https://developers.deepl.com/docs/v/zh/api-reference/translate?fallback=true
|
||||
|
||||
Below are test cases. For stability, it is recommended to maintain a stable version of the large model. The example used here is qwen-max-0403:
|
||||
**Request Example**
|
||||
```shell
|
||||
curl 'http://<replace with gateway public IP>/api/openai/v1/chat/completions' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"I want to have coffee near the Xinshi Building in Jinan, please recommend a few."}],"presence_penalty":0,"temperature":0,"top_p":0}'
|
||||
```
|
||||
**Response Example**
|
||||
```json
|
||||
{"id":"139487e7-96a0-9b13-91b4-290fb79ac992","choices":[{"index":0,"message":{"role":"assistant","content":" Near the Xinshi Building in Jinan, you can choose from the following coffee shops:\n1. luckin coffee 瑞幸咖啡(鑫盛大厦店), located in the lobby of Xinshi Building, No. 1299 Xinluo Avenue;\n2. 三庆齐盛广场挪瓦咖啡(三庆·齐盛广场店), located 60 meters southwest of the intersection of Xinluo Avenue and Yingxiu Road;\n3. luckin coffee 瑞幸咖啡(三庆·齐盛广场店), located at No. 1267 Yingxiu Road;\n4. 库迪咖啡(齐鲁软件园店), located in the commercial space of Building 4, Sanqing Qisheng Plaza, Xinluo Avenue;\n5. 库迪咖啡(美莲广场店), located at L117, Meilian Plaza, No. 1166 Xinluo Avenue, High-tech Zone; and a few other options. I hope these suggestions help!"},"finish_reason":"stop"}],"created":1723172296,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":886,"completion_tokens":50,"total_tokens":936}}
|
||||
```
|
||||
**Request Example**
|
||||
```shell
|
||||
curl 'http://<replace with gateway public IP>/api/openai/v1/chat/completions' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"What is the current weather in Jinan?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
|
||||
```
|
||||
**Response Example**
|
||||
```json
|
||||
{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content":" The current weather condition in Jinan is overcast, with a temperature of 31°C. This information was last updated on August 9, 2024, at 15:12 (Beijing time)."},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}}
|
||||
```
|
||||
**Request Example**
|
||||
```shell
|
||||
curl 'http://<replace with gateway public IP>/api/openai/v1/chat/completions' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"What is the current weather in Jinan?"},{"role":"assistant","content":" The current weather condition in Jinan is overcast, with a temperature of 31°C. This information was last updated on August 9, 2024, at 15:12 (Beijing time)."},{"role":"user","content":"BeiJing?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
|
||||
```
|
||||
**Response Example**
|
||||
```json
|
||||
{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content":" The current weather condition in Beijing is overcast, with a temperature of 19°C. This information was last updated on Sep 12, 2024, at 22:17 (Beijing time)."},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":999,"completion_tokens":76,"total_tokens":1075}}
|
||||
```
|
||||
**Request Example**
|
||||
```shell
|
||||
curl 'http://<replace with gateway public IP>/api/openai/v1/chat/completions' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"What is the current weather in Jinan? Please indicate in Fahrenheit and respond in Japanese."}],"presence_penalty":0,"temperature":0,"top_p":0}'
|
||||
```
|
||||
**Response Example**
|
||||
```json
|
||||
{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content":" 現在の济南の天気は曇りで、気温は88°Fです。この情報は2024年8月9日15時12分(東京時間)に更新されました。"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}}
|
||||
```
|
||||
**Request Example**
|
||||
```shell
|
||||
curl 'http://<replace with gateway public IP>/api/openai/v1/chat/completions' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"Help me translate the following sentence into German: \"Hail Hydra!\""}],"presence_penalty":0,"temperature":0,"top_p":0}'
|
||||
```
|
||||
**Response Example**
|
||||
```json
|
||||
{"id":"65dcf12c-61ff-9e68-bffa-44fc9e6070d5","choices":[{"index":0,"message":{"role":"assistant","content":" The German translation of \"Hail Hydra!\" is \"Hoch lebe Hydra!\"."},"finish_reason":"stop"}],"created":1724043865,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":908,"completion_tokens":52,"total_tokens":960}}
|
||||
```
|
||||
|
||||
@@ -46,7 +46,7 @@ type Response struct {
|
||||
}
|
||||
|
||||
// 用于存放拆解出来的工具相关信息
|
||||
type Tool_Param struct {
|
||||
type ToolsParam struct {
|
||||
ToolName string `yaml:"toolName"`
|
||||
Path string `yaml:"path"`
|
||||
Method string `yaml:"method"`
|
||||
@@ -56,10 +56,11 @@ type Tool_Param struct {
|
||||
}
|
||||
|
||||
// 用于存放拆解出来的api相关信息
|
||||
type APIParam struct {
|
||||
APIKey APIKey `yaml:"apiKey"`
|
||||
URL string `yaml:"url"`
|
||||
Tool_Param []Tool_Param `yaml:"tool_Param"`
|
||||
type APIsParam struct {
|
||||
APIKey APIKey `yaml:"apiKey"`
|
||||
URL string `yaml:"url"`
|
||||
MaxExecutionTime int64 `yaml:"maxExecutionTime"`
|
||||
ToolsParam []ToolsParam `yaml:"toolsParam"`
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
@@ -153,7 +154,10 @@ type APIProvider struct {
|
||||
ServicePort int64 `required:"true" yaml:"servicePort" json:"servicePort"`
|
||||
// @Title zh-CN 服务域名
|
||||
// @Description zh-CN 服务域名,例如 restapi.amap.com
|
||||
Domin string `required:"true" yaml:"domain" json:"domain"`
|
||||
Domain string `required:"true" yaml:"domain" json:"domain"`
|
||||
// @Title zh-CN 每一次请求api的超时时间
|
||||
// @Description zh-CN 每一次请求api的超时时间,单位毫秒,默认50000
|
||||
MaxExecutionTime int64 `yaml:"maxExecutionTime" json:"maxExecutionTime"`
|
||||
// @Title zh-CN 通义千问大模型服务的key
|
||||
// @Description zh-CN 通义千问大模型服务的key
|
||||
APIKey APIKey `required:"true" yaml:"apiKey" json:"apiKey"`
|
||||
@@ -167,11 +171,8 @@ type APIs struct {
|
||||
type Template struct {
|
||||
Question string `yaml:"question" json:"question"`
|
||||
Thought1 string `yaml:"thought1" json:"thought1"`
|
||||
ActionInput string `yaml:"actionInput" json:"actionInput"`
|
||||
Observation string `yaml:"observation" json:"observation"`
|
||||
Thought2 string `yaml:"thought2" json:"thought2"`
|
||||
FinalAnswer string `yaml:"finalAnswer" json:"finalAnswer"`
|
||||
Begin string `yaml:"begin" json:"begin"`
|
||||
}
|
||||
|
||||
type PromptTemplate struct {
|
||||
@@ -189,7 +190,7 @@ type LLMInfo struct {
|
||||
ServicePort int64 `required:"true" yaml:"servicePort" json:"servicePort"`
|
||||
// @Title zh-CN 大模型服务域名
|
||||
// @Description zh-CN 大模型服务域名,例如 dashscope.aliyuncs.com
|
||||
Domin string `required:"true" yaml:"domin" json:"domin"`
|
||||
Domain string `required:"true" yaml:"domain" json:"domain"`
|
||||
// @Title zh-CN 大模型服务的key
|
||||
// @Description zh-CN 大模型服务的key
|
||||
APIKey string `required:"true" yaml:"apiKey" json:"apiKey"`
|
||||
@@ -222,7 +223,7 @@ type PluginConfig struct {
|
||||
// @Description zh-CN 用于存储llm使用信息
|
||||
LLMInfo LLMInfo `required:"true" yaml:"llm" json:"llm"`
|
||||
LLMClient wrapper.HttpClient `yaml:"-" json:"-"`
|
||||
APIParam []APIParam `yaml:"-" json:"-"`
|
||||
APIsParam []APIsParam `yaml:"-" json:"-"`
|
||||
PromptTemplate PromptTemplate `yaml:"promptTemplate" json:"promptTemplate"`
|
||||
}
|
||||
|
||||
@@ -260,11 +261,13 @@ func initAPIs(gjson gjson.Result, c *PluginConfig) error {
|
||||
return errors.New("apiProvider domain is required")
|
||||
}
|
||||
|
||||
apiKeyIn := item.Get("apiProvider.apiKey.in").String()
|
||||
if apiKeyIn != "query" {
|
||||
apiKeyIn = "header"
|
||||
maxExecutionTime := item.Get("apiProvider.maxExecutionTime").Int()
|
||||
if maxExecutionTime == 0 {
|
||||
maxExecutionTime = 50000
|
||||
}
|
||||
|
||||
apiKeyIn := item.Get("apiProvider.apiKey.in").String()
|
||||
|
||||
apiKeyName := item.Get("apiProvider.apiKey.name")
|
||||
|
||||
apiKeyValue := item.Get("apiProvider.apiKey.value")
|
||||
@@ -289,13 +292,13 @@ func initAPIs(gjson gjson.Result, c *PluginConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var allTool_param []Tool_Param
|
||||
var allTool_param []ToolsParam
|
||||
//拆除服务下面的每个api的path
|
||||
for path, pathmap := range apiStruct.Paths {
|
||||
//拆解出每个api对应的参数
|
||||
for method, submap := range pathmap {
|
||||
//把参数列表存起来
|
||||
var param Tool_Param
|
||||
var param ToolsParam
|
||||
param.Path = path
|
||||
param.ToolName = submap.OperationID
|
||||
if method == "get" {
|
||||
@@ -319,13 +322,14 @@ func initAPIs(gjson gjson.Result, c *PluginConfig) error {
|
||||
allTool_param = append(allTool_param, param)
|
||||
}
|
||||
}
|
||||
apiParam := APIParam{
|
||||
APIKey: APIKey{In: apiKeyIn, Name: apiKeyName.String(), Value: apiKeyValue.String()},
|
||||
URL: apiStruct.Servers[0].URL,
|
||||
Tool_Param: allTool_param,
|
||||
apiParam := APIsParam{
|
||||
APIKey: APIKey{In: apiKeyIn, Name: apiKeyName.String(), Value: apiKeyValue.String()},
|
||||
URL: apiStruct.Servers[0].URL,
|
||||
MaxExecutionTime: maxExecutionTime,
|
||||
ToolsParam: allTool_param,
|
||||
}
|
||||
|
||||
c.APIParam = append(c.APIParam, apiParam)
|
||||
c.APIsParam = append(c.APIsParam, apiParam)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -338,60 +342,36 @@ func initReActPromptTpl(gjson gjson.Result, c *PluginConfig) {
|
||||
if c.PromptTemplate.Language == "EN" {
|
||||
c.PromptTemplate.ENTemplate.Question = gjson.Get("promptTemplate.enTemplate.question").String()
|
||||
if c.PromptTemplate.ENTemplate.Question == "" {
|
||||
c.PromptTemplate.ENTemplate.Question = "the input question you must answer"
|
||||
c.PromptTemplate.ENTemplate.Question = "input question to answer"
|
||||
}
|
||||
c.PromptTemplate.ENTemplate.Thought1 = gjson.Get("promptTemplate.enTemplate.thought1").String()
|
||||
if c.PromptTemplate.ENTemplate.Thought1 == "" {
|
||||
c.PromptTemplate.ENTemplate.Thought1 = "you should always think about what to do"
|
||||
}
|
||||
c.PromptTemplate.ENTemplate.ActionInput = gjson.Get("promptTemplate.enTemplate.actionInput").String()
|
||||
if c.PromptTemplate.ENTemplate.ActionInput == "" {
|
||||
c.PromptTemplate.ENTemplate.ActionInput = "the input to the action"
|
||||
c.PromptTemplate.ENTemplate.Thought1 = "consider previous and subsequent steps"
|
||||
}
|
||||
c.PromptTemplate.ENTemplate.Observation = gjson.Get("promptTemplate.enTemplate.observation").String()
|
||||
if c.PromptTemplate.ENTemplate.Observation == "" {
|
||||
c.PromptTemplate.ENTemplate.Observation = "the result of the action"
|
||||
c.PromptTemplate.ENTemplate.Observation = "action result"
|
||||
}
|
||||
c.PromptTemplate.ENTemplate.Thought1 = gjson.Get("promptTemplate.enTemplate.thought2").String()
|
||||
if c.PromptTemplate.ENTemplate.Thought1 == "" {
|
||||
c.PromptTemplate.ENTemplate.Thought1 = "I now know the final answer"
|
||||
}
|
||||
c.PromptTemplate.ENTemplate.FinalAnswer = gjson.Get("promptTemplate.enTemplate.finalAnswer").String()
|
||||
if c.PromptTemplate.ENTemplate.FinalAnswer == "" {
|
||||
c.PromptTemplate.ENTemplate.FinalAnswer = "the final answer to the original input question, please give the most direct answer directly in Chinese, not English, and do not add extra content."
|
||||
}
|
||||
c.PromptTemplate.ENTemplate.Begin = gjson.Get("promptTemplate.enTemplate.begin").String()
|
||||
if c.PromptTemplate.ENTemplate.Begin == "" {
|
||||
c.PromptTemplate.ENTemplate.Begin = "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Arg\"s"
|
||||
c.PromptTemplate.ENTemplate.Thought2 = gjson.Get("promptTemplate.enTemplate.thought2").String()
|
||||
if c.PromptTemplate.ENTemplate.Thought2 == "" {
|
||||
c.PromptTemplate.ENTemplate.Thought2 = "I know what to respond"
|
||||
}
|
||||
} else if c.PromptTemplate.Language == "CH" {
|
||||
c.PromptTemplate.CHTemplate.Question = gjson.Get("promptTemplate.chTemplate.question").String()
|
||||
if c.PromptTemplate.CHTemplate.Question == "" {
|
||||
c.PromptTemplate.CHTemplate.Question = "你需要回答的输入问题"
|
||||
c.PromptTemplate.CHTemplate.Question = "输入要回答的问题"
|
||||
}
|
||||
c.PromptTemplate.CHTemplate.Thought1 = gjson.Get("promptTemplate.chTemplate.thought1").String()
|
||||
if c.PromptTemplate.CHTemplate.Thought1 == "" {
|
||||
c.PromptTemplate.CHTemplate.Thought1 = "你应该总是思考该做什么"
|
||||
}
|
||||
c.PromptTemplate.CHTemplate.ActionInput = gjson.Get("promptTemplate.chTemplate.actionInput").String()
|
||||
if c.PromptTemplate.CHTemplate.ActionInput == "" {
|
||||
c.PromptTemplate.CHTemplate.ActionInput = "行动的输入,必须出现在Action后"
|
||||
c.PromptTemplate.CHTemplate.Thought1 = "考虑之前和之后的步骤"
|
||||
}
|
||||
c.PromptTemplate.CHTemplate.Observation = gjson.Get("promptTemplate.chTemplate.observation").String()
|
||||
if c.PromptTemplate.CHTemplate.Observation == "" {
|
||||
c.PromptTemplate.CHTemplate.Observation = "行动的结果"
|
||||
c.PromptTemplate.CHTemplate.Observation = "行动结果"
|
||||
}
|
||||
c.PromptTemplate.CHTemplate.Thought1 = gjson.Get("promptTemplate.chTemplate.thought2").String()
|
||||
if c.PromptTemplate.CHTemplate.Thought1 == "" {
|
||||
c.PromptTemplate.CHTemplate.Thought1 = "我现在知道最终答案"
|
||||
}
|
||||
c.PromptTemplate.CHTemplate.FinalAnswer = gjson.Get("promptTemplate.chTemplate.finalAnswer").String()
|
||||
if c.PromptTemplate.CHTemplate.FinalAnswer == "" {
|
||||
c.PromptTemplate.CHTemplate.FinalAnswer = "对原始输入问题的最终答案"
|
||||
}
|
||||
c.PromptTemplate.CHTemplate.Begin = gjson.Get("promptTemplate.chTemplate.begin").String()
|
||||
if c.PromptTemplate.CHTemplate.Begin == "" {
|
||||
c.PromptTemplate.CHTemplate.Begin = "再次重申,不要修改以上模板的字段名称,开始吧!"
|
||||
c.PromptTemplate.CHTemplate.Thought2 = gjson.Get("promptTemplate.chTemplate.thought2").String()
|
||||
if c.PromptTemplate.CHTemplate.Thought2 == "" {
|
||||
c.PromptTemplate.CHTemplate.Thought2 = "我知道该回应什么"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,7 +380,7 @@ func initLLMClient(gjson gjson.Result, c *PluginConfig) {
|
||||
c.LLMInfo.APIKey = gjson.Get("llm.apiKey").String()
|
||||
c.LLMInfo.ServiceName = gjson.Get("llm.serviceName").String()
|
||||
c.LLMInfo.ServicePort = gjson.Get("llm.servicePort").Int()
|
||||
c.LLMInfo.Domin = gjson.Get("llm.domain").String()
|
||||
c.LLMInfo.Domain = gjson.Get("llm.domain").String()
|
||||
c.LLMInfo.Path = gjson.Get("llm.path").String()
|
||||
c.LLMInfo.Model = gjson.Get("llm.model").String()
|
||||
c.LLMInfo.MaxIterations = gjson.Get("llm.maxIterations").Int()
|
||||
@@ -419,6 +399,6 @@ func initLLMClient(gjson gjson.Result, c *PluginConfig) {
|
||||
c.LLMClient = wrapper.NewClusterClient(wrapper.FQDNCluster{
|
||||
FQDN: c.LLMInfo.ServiceName,
|
||||
Port: c.LLMInfo.ServicePort,
|
||||
Host: c.LLMInfo.Domin,
|
||||
Host: c.LLMInfo.Domain,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
// 用于统计函数的递归调用次数
|
||||
const ToolCallsCount = "ToolCallsCount"
|
||||
const StreamContextKey = "Stream"
|
||||
|
||||
// react的正则规则
|
||||
const ActionPattern = `Action:\s*(.*?)[.\n]`
|
||||
@@ -53,7 +54,7 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrap
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func firstReq(config PluginConfig, prompt string, rawRequest Request, log wrapper.Log) types.Action {
|
||||
func firstReq(ctx wrapper.HttpContext, config PluginConfig, prompt string, rawRequest Request, log wrapper.Log) types.Action {
|
||||
log.Debugf("[onHttpRequestBody] firstreq:%s", prompt)
|
||||
|
||||
var userMessage Message
|
||||
@@ -62,13 +63,17 @@ func firstReq(config PluginConfig, prompt string, rawRequest Request, log wrappe
|
||||
|
||||
newMessages := []Message{userMessage}
|
||||
rawRequest.Messages = newMessages
|
||||
if rawRequest.Stream {
|
||||
ctx.SetContext(StreamContextKey, struct{}{})
|
||||
rawRequest.Stream = false
|
||||
}
|
||||
|
||||
//replace old message and resume request qwen
|
||||
newbody, err := json.Marshal(rawRequest)
|
||||
if err != nil {
|
||||
return types.ActionContinue
|
||||
} else {
|
||||
log.Debugf("[onHttpRequestBody] newRequestBody: ", string(newbody))
|
||||
log.Debugf("[onHttpRequestBody] newRequestBody: %s", string(newbody))
|
||||
err := proxywasm.ReplaceHttpRequestBody(newbody)
|
||||
if err != nil {
|
||||
log.Debug("替换失败")
|
||||
@@ -87,18 +92,26 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte
|
||||
var rawRequest Request
|
||||
err := json.Unmarshal(body, &rawRequest)
|
||||
if err != nil {
|
||||
log.Debugf("[onHttpRequestBody] body json umarshal err: ", err.Error())
|
||||
log.Debugf("[onHttpRequestBody] body json umarshal err: %s", err.Error())
|
||||
return types.ActionContinue
|
||||
}
|
||||
log.Debugf("onHttpRequestBody rawRequest: %v", rawRequest)
|
||||
|
||||
//获取用户query
|
||||
var query string
|
||||
var history string
|
||||
messageLength := len(rawRequest.Messages)
|
||||
log.Debugf("[onHttpRequestBody] messageLength: %s\n", messageLength)
|
||||
log.Debugf("[onHttpRequestBody] messageLength: %s", messageLength)
|
||||
if messageLength > 0 {
|
||||
query = rawRequest.Messages[messageLength-1].Content
|
||||
log.Debugf("[onHttpRequestBody] query: %s\n", query)
|
||||
log.Debugf("[onHttpRequestBody] query: %s", query)
|
||||
if messageLength >= 3 {
|
||||
for i := 0; i < messageLength-1; i += 2 {
|
||||
history += "human: " + rawRequest.Messages[i].Content + "\nAI: " + rawRequest.Messages[i+1].Content
|
||||
}
|
||||
} else {
|
||||
history = ""
|
||||
}
|
||||
} else {
|
||||
return types.ActionContinue
|
||||
}
|
||||
@@ -111,8 +124,8 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte
|
||||
//拼装agent prompt模板
|
||||
tool_desc := make([]string, 0)
|
||||
tool_names := make([]string, 0)
|
||||
for _, apiParam := range config.APIParam {
|
||||
for _, tool_param := range apiParam.Tool_Param {
|
||||
for _, apisParam := range config.APIsParam {
|
||||
for _, tool_param := range apisParam.ToolsParam {
|
||||
tool_desc = append(tool_desc, fmt.Sprintf(prompttpl.TOOL_DESC, tool_param.ToolName, tool_param.Description, tool_param.Description, tool_param.Description, tool_param.Parameter), "\n")
|
||||
tool_names = append(tool_names, tool_param.ToolName)
|
||||
}
|
||||
@@ -122,26 +135,22 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte
|
||||
if config.PromptTemplate.Language == "CH" {
|
||||
prompt = fmt.Sprintf(prompttpl.CH_Template,
|
||||
tool_desc,
|
||||
tool_names,
|
||||
config.PromptTemplate.CHTemplate.Question,
|
||||
config.PromptTemplate.CHTemplate.Thought1,
|
||||
tool_names,
|
||||
config.PromptTemplate.CHTemplate.ActionInput,
|
||||
config.PromptTemplate.CHTemplate.Observation,
|
||||
config.PromptTemplate.CHTemplate.Thought2,
|
||||
config.PromptTemplate.CHTemplate.FinalAnswer,
|
||||
config.PromptTemplate.CHTemplate.Begin,
|
||||
history,
|
||||
query)
|
||||
} else {
|
||||
prompt = fmt.Sprintf(prompttpl.EN_Template,
|
||||
tool_desc,
|
||||
tool_names,
|
||||
config.PromptTemplate.ENTemplate.Question,
|
||||
config.PromptTemplate.ENTemplate.Thought1,
|
||||
tool_names,
|
||||
config.PromptTemplate.ENTemplate.ActionInput,
|
||||
config.PromptTemplate.ENTemplate.Observation,
|
||||
config.PromptTemplate.ENTemplate.Thought2,
|
||||
config.PromptTemplate.ENTemplate.FinalAnswer,
|
||||
config.PromptTemplate.ENTemplate.Begin,
|
||||
history,
|
||||
query)
|
||||
}
|
||||
|
||||
@@ -154,7 +163,7 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte
|
||||
dashscope.MessageStore.AddForUser(prompt)
|
||||
|
||||
//开始第一次请求
|
||||
ret := firstReq(config, prompt, rawRequest, log)
|
||||
ret := firstReq(ctx, config, prompt, rawRequest, log)
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -168,7 +177,7 @@ func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig, log wra
|
||||
|
||||
func toolsCallResult(ctx wrapper.HttpContext, config PluginConfig, content string, rawResponse Response, log wrapper.Log, statusCode int, responseBody []byte) {
|
||||
if statusCode != http.StatusOK {
|
||||
log.Debugf("statusCode: %d\n", statusCode)
|
||||
log.Debugf("statusCode: %d", statusCode)
|
||||
}
|
||||
log.Info("========函数返回结果========")
|
||||
log.Infof(string(responseBody))
|
||||
@@ -193,30 +202,36 @@ func toolsCallResult(ctx wrapper.HttpContext, config PluginConfig, content strin
|
||||
//得到gpt的返回结果
|
||||
var responseCompletion dashscope.CompletionResponse
|
||||
_ = json.Unmarshal(responseBody, &responseCompletion)
|
||||
log.Infof("[toolsCall] content: %s\n", responseCompletion.Choices[0].Message.Content)
|
||||
log.Infof("[toolsCall] content: %s", responseCompletion.Choices[0].Message.Content)
|
||||
|
||||
if responseCompletion.Choices[0].Message.Content != "" {
|
||||
retType := toolsCall(ctx, config, responseCompletion.Choices[0].Message.Content, rawResponse, log)
|
||||
retType, actionInput := toolsCall(ctx, config, responseCompletion.Choices[0].Message.Content, rawResponse, log)
|
||||
if retType == types.ActionContinue {
|
||||
//得到了Final Answer
|
||||
var assistantMessage Message
|
||||
assistantMessage.Role = "assistant"
|
||||
startIndex := strings.Index(responseCompletion.Choices[0].Message.Content, "Final Answer:")
|
||||
if startIndex != -1 {
|
||||
startIndex += len("Final Answer:") // 移动到"Final Answer:"之后的位置
|
||||
extractedText := responseCompletion.Choices[0].Message.Content[startIndex:]
|
||||
assistantMessage.Content = extractedText
|
||||
}
|
||||
if ctx.GetContext(StreamContextKey) == nil {
|
||||
assistantMessage.Role = "assistant"
|
||||
assistantMessage.Content = actionInput
|
||||
rawResponse.Choices[0].Message = assistantMessage
|
||||
newbody, err := json.Marshal(rawResponse)
|
||||
if err != nil {
|
||||
proxywasm.ResumeHttpResponse()
|
||||
return
|
||||
} else {
|
||||
proxywasm.ReplaceHttpResponseBody(newbody)
|
||||
|
||||
rawResponse.Choices[0].Message = assistantMessage
|
||||
|
||||
newbody, err := json.Marshal(rawResponse)
|
||||
if err != nil {
|
||||
proxywasm.ResumeHttpResponse()
|
||||
return
|
||||
log.Debug("[onHttpResponseBody] response替换成功")
|
||||
proxywasm.ResumeHttpResponse()
|
||||
}
|
||||
} else {
|
||||
log.Infof("[onHttpResponseBody] newResponseBody: ", string(newbody))
|
||||
proxywasm.ReplaceHttpResponseBody(newbody)
|
||||
headers := [][2]string{{"content-type", "text/event-stream; charset=utf-8"}}
|
||||
proxywasm.ReplaceHttpResponseHeaders(headers)
|
||||
// Remove quotes from actionInput
|
||||
actionInput = strings.Trim(actionInput, "\"")
|
||||
returnStreamResponseTemplate := `data:{"id":"%s","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"%s","object":"chat.completion","usage":{"prompt_tokens":%d,"completion_tokens":%d,"total_tokens":%d}}` + "\n\ndata:[DONE]\n\n"
|
||||
newbody := fmt.Sprintf(returnStreamResponseTemplate, rawResponse.ID, actionInput, rawResponse.Model, rawResponse.Usage.PromptTokens, rawResponse.Usage.CompletionTokens, rawResponse.Usage.TotalTokens)
|
||||
log.Infof("[onHttpResponseBody] newResponseBody: ", newbody)
|
||||
proxywasm.ReplaceHttpResponseBody([]byte(newbody))
|
||||
|
||||
log.Debug("[onHttpResponseBody] response替换成功")
|
||||
proxywasm.ResumeHttpResponse()
|
||||
@@ -232,121 +247,156 @@ func toolsCallResult(ctx wrapper.HttpContext, config PluginConfig, content strin
|
||||
}
|
||||
}
|
||||
|
||||
func toolsCall(ctx wrapper.HttpContext, config PluginConfig, content string, rawResponse Response, log wrapper.Log) types.Action {
|
||||
func outputParser(response string, log wrapper.Log) (string, string) {
|
||||
log.Debugf("Raw response:%s", response)
|
||||
|
||||
start := strings.Index(response, "```")
|
||||
end := strings.LastIndex(response, "```")
|
||||
|
||||
var jsonStr string
|
||||
if start != -1 && end != -1 {
|
||||
jsonStr = strings.TrimSpace(response[start+3 : end])
|
||||
} else {
|
||||
jsonStr = response
|
||||
}
|
||||
|
||||
log.Debugf("Extracted JSON string:%s", jsonStr)
|
||||
|
||||
var action map[string]interface{}
|
||||
err := json.Unmarshal([]byte(jsonStr), &action)
|
||||
if err == nil {
|
||||
var actionName, actionInput string
|
||||
for key, value := range action {
|
||||
if strings.Contains(strings.ToLower(key), "input") {
|
||||
actionInput = fmt.Sprintf("%v", value)
|
||||
} else {
|
||||
actionName = fmt.Sprintf("%v", value)
|
||||
}
|
||||
}
|
||||
if actionName != "" && actionInput != "" {
|
||||
return actionName, actionInput
|
||||
}
|
||||
}
|
||||
log.Debugf("json parse err: %s", err.Error())
|
||||
// Fallback to regex parsing if JSON unmarshaling fails
|
||||
pattern := `\{\s*"action":\s*"([^"]+)",\s*"action_input":\s*((?:\{[^}]+\})|"[^"]+")\s*\}`
|
||||
re := regexp.MustCompile(pattern)
|
||||
match := re.FindStringSubmatch(jsonStr)
|
||||
|
||||
if len(match) == 3 {
|
||||
action := match[1]
|
||||
actionInput := match[2]
|
||||
log.Debugf("Parsed action:%s, action_input:%s", action, actionInput)
|
||||
return action, actionInput
|
||||
}
|
||||
|
||||
log.Debug("No valid action and action_input found in the response")
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func toolsCall(ctx wrapper.HttpContext, config PluginConfig, content string, rawResponse Response, log wrapper.Log) (types.Action, string) {
|
||||
dashscope.MessageStore.AddForAssistant(content)
|
||||
|
||||
action, actionInput := outputParser(content, log)
|
||||
|
||||
//得到最终答案
|
||||
regexPattern := regexp.MustCompile(FinalAnswerPattern)
|
||||
finalAnswer := regexPattern.FindStringSubmatch(content)
|
||||
if len(finalAnswer) > 1 {
|
||||
return types.ActionContinue
|
||||
if action == "Final Answer" {
|
||||
return types.ActionContinue, actionInput
|
||||
}
|
||||
count := ctx.GetContext(ToolCallsCount).(int)
|
||||
count++
|
||||
log.Debugf("toolCallsCount:%d, config.LLMInfo.MaxIterations=%d\n", count, config.LLMInfo.MaxIterations)
|
||||
log.Debugf("toolCallsCount:%d, config.LLMInfo.MaxIterations=%d", count, config.LLMInfo.MaxIterations)
|
||||
//函数递归调用次数,达到了预设的循环次数,强制结束
|
||||
if int64(count) > config.LLMInfo.MaxIterations {
|
||||
ctx.SetContext(ToolCallsCount, 0)
|
||||
return types.ActionContinue
|
||||
return types.ActionContinue, ""
|
||||
} else {
|
||||
ctx.SetContext(ToolCallsCount, count)
|
||||
}
|
||||
|
||||
//没得到最终答案
|
||||
regexAction := regexp.MustCompile(ActionPattern)
|
||||
regexActionInput := regexp.MustCompile(ActionInputPattern)
|
||||
|
||||
action := regexAction.FindStringSubmatch(content)
|
||||
actionInput := regexActionInput.FindStringSubmatch(content)
|
||||
var url string
|
||||
var headers [][2]string
|
||||
var apiClient wrapper.HttpClient
|
||||
var method string
|
||||
var reqBody []byte
|
||||
var key string
|
||||
var maxExecutionTime int64
|
||||
|
||||
if len(action) > 1 && len(actionInput) > 1 {
|
||||
var url string
|
||||
var headers [][2]string
|
||||
var apiClient wrapper.HttpClient
|
||||
var method string
|
||||
var reqBody []byte
|
||||
var key string
|
||||
for i, apisParam := range config.APIsParam {
|
||||
maxExecutionTime = apisParam.MaxExecutionTime
|
||||
for _, tools_param := range apisParam.ToolsParam {
|
||||
if action == tools_param.ToolName {
|
||||
log.Infof("calls %s", tools_param.ToolName)
|
||||
log.Infof("actionInput: %s", actionInput)
|
||||
|
||||
for i, apiParam := range config.APIParam {
|
||||
for _, tool_param := range apiParam.Tool_Param {
|
||||
if action[1] == tool_param.ToolName {
|
||||
log.Infof("calls %s\n", tool_param.ToolName)
|
||||
log.Infof("actionInput[1]: %s", actionInput[1])
|
||||
|
||||
//将大模型需要的参数反序列化
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(actionInput[1]), &data); err != nil {
|
||||
log.Debugf("Error: %s\n", err.Error())
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
method = tool_param.Method
|
||||
|
||||
//key or header组装
|
||||
if apiParam.APIKey.Name != "" {
|
||||
if apiParam.APIKey.In == "query" { //query类型的key要放到url中
|
||||
headers = nil
|
||||
key = "?" + apiParam.APIKey.Name + "=" + apiParam.APIKey.Value
|
||||
} else if apiParam.APIKey.In == "header" { //header类型的key放在header中
|
||||
headers = [][2]string{{"Content-Type", "application/json"}, {"Authorization", apiParam.APIKey.Name + " " + apiParam.APIKey.Value}}
|
||||
}
|
||||
}
|
||||
|
||||
if method == "GET" {
|
||||
//query组装
|
||||
var args string
|
||||
for i, param := range tool_param.ParamName { //从参数列表中取出参数
|
||||
if i == 0 && apiParam.APIKey.In != "query" {
|
||||
args = "?" + param + "=%s"
|
||||
args = fmt.Sprintf(args, data[param])
|
||||
} else {
|
||||
args = args + "&" + param + "=%s"
|
||||
args = fmt.Sprintf(args, data[param])
|
||||
}
|
||||
}
|
||||
|
||||
//url组装
|
||||
url = apiParam.URL + tool_param.Path + key + args
|
||||
} else if method == "POST" {
|
||||
reqBody = nil
|
||||
//json参数组装
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Debugf("Error: %s\n", err.Error())
|
||||
return types.ActionContinue
|
||||
}
|
||||
reqBody = jsonData
|
||||
|
||||
//url组装
|
||||
url = apiParam.URL + tool_param.Path + key
|
||||
}
|
||||
|
||||
log.Infof("url: %s\n", url)
|
||||
|
||||
apiClient = config.APIClient[i]
|
||||
break
|
||||
//将大模型需要的参数反序列化
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(actionInput), &data); err != nil {
|
||||
log.Debugf("Error: %s", err.Error())
|
||||
return types.ActionContinue, ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if apiClient != nil {
|
||||
err := apiClient.Call(
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
reqBody,
|
||||
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
toolsCallResult(ctx, config, content, rawResponse, log, statusCode, responseBody)
|
||||
}, 50000)
|
||||
if err != nil {
|
||||
log.Debugf("tool calls error: %s\n", err.Error())
|
||||
proxywasm.ResumeHttpRequest()
|
||||
method = tools_param.Method
|
||||
|
||||
// 组装 headers 和 key
|
||||
headers = [][2]string{{"Content-Type", "application/json"}}
|
||||
if apisParam.APIKey.Name != "" {
|
||||
if apisParam.APIKey.In == "query" {
|
||||
key = "?" + apisParam.APIKey.Name + "=" + apisParam.APIKey.Value
|
||||
} else if apisParam.APIKey.In == "header" {
|
||||
headers = append(headers, [2]string{"Authorization", apisParam.APIKey.Name + " " + apisParam.APIKey.Value})
|
||||
}
|
||||
}
|
||||
|
||||
// 组装 URL 和请求体
|
||||
url = apisParam.URL + tools_param.Path + key
|
||||
if method == "GET" {
|
||||
queryParams := make([]string, 0, len(tools_param.ParamName))
|
||||
for _, param := range tools_param.ParamName {
|
||||
if value, ok := data[param]; ok {
|
||||
queryParams = append(queryParams, fmt.Sprintf("%s=%v", param, value))
|
||||
}
|
||||
}
|
||||
if len(queryParams) > 0 {
|
||||
url += "&" + strings.Join(queryParams, "&")
|
||||
}
|
||||
} else if method == "POST" {
|
||||
var err error
|
||||
reqBody, err = json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Debugf("Error marshaling JSON: %s", err.Error())
|
||||
return types.ActionContinue, ""
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("url: %s", url)
|
||||
|
||||
apiClient = config.APIClient[i]
|
||||
break
|
||||
}
|
||||
} else {
|
||||
return types.ActionContinue
|
||||
}
|
||||
}
|
||||
return types.ActionPause
|
||||
|
||||
if apiClient != nil {
|
||||
err := apiClient.Call(
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
reqBody,
|
||||
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
toolsCallResult(ctx, config, content, rawResponse, log, statusCode, responseBody)
|
||||
}, uint32(maxExecutionTime))
|
||||
if err != nil {
|
||||
log.Debugf("tool calls error: %s", err.Error())
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
} else {
|
||||
return types.ActionContinue, ""
|
||||
}
|
||||
|
||||
return types.ActionPause, ""
|
||||
}
|
||||
|
||||
// 从response接收到firstreq的大模型返回
|
||||
@@ -361,11 +411,12 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config PluginConfig, body []byt
|
||||
log.Debugf("[onHttpResponseBody] body to json err: %s", err.Error())
|
||||
return types.ActionContinue
|
||||
}
|
||||
log.Infof("first content: %s\n", rawResponse.Choices[0].Message.Content)
|
||||
log.Infof("first content: %s", rawResponse.Choices[0].Message.Content)
|
||||
//如果gpt返回的内容不是空的
|
||||
if rawResponse.Choices[0].Message.Content != "" {
|
||||
//进入agent的循环思考,工具调用的过程中
|
||||
return toolsCall(ctx, config, rawResponse.Choices[0].Message.Content, rawResponse, log)
|
||||
retType, _ := toolsCall(ctx, config, rawResponse.Choices[0].Message.Content, rawResponse, log)
|
||||
return retType
|
||||
} else {
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
@@ -13,81 +13,157 @@ Parameters:
|
||||
Format the arguments as a JSON object.`
|
||||
|
||||
/*
|
||||
Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools:
|
||||
Respond to the human as helpfully and accurately as possible. You have access to the following tools:
|
||||
|
||||
%s
|
||||
{{tools_desc}}
|
||||
|
||||
Use the following format:
|
||||
Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).
|
||||
Valid "action" values: "Final Answer" or {{tool_names}}
|
||||
|
||||
Question: the input question you must answer
|
||||
Thought: you should always think about what to do
|
||||
Action: the action to take, should be one of %s
|
||||
Action Input: the input to the action
|
||||
Observation: the result of the action
|
||||
... (this Thought/Action/Action Input/Observation can repeat N times)
|
||||
Thought: I now know the final answer
|
||||
Final Answer: the final answer to the original input question, please give the most direct answer directly in Chinese, not English, and do not add extra content.
|
||||
Provide only ONE action per $JSON_BLOB, as shown:
|
||||
|
||||
Begin! Remember to speak as a pirate when giving your final answer. Use lots of "Arg"s
|
||||
```
|
||||
|
||||
Question: %s
|
||||
{
|
||||
"action": $TOOL_NAME,
|
||||
"action_input": $ACTION_INPUT
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Follow this format:
|
||||
|
||||
Question: input question to answer
|
||||
Thought: consider previous and subsequent steps
|
||||
Action:
|
||||
```
|
||||
$JSON_BLOB
|
||||
```
|
||||
Observation: action result
|
||||
... (repeat Thought/Action/Observation N times)
|
||||
Thought: I know what to respond
|
||||
Action:
|
||||
```
|
||||
|
||||
{
|
||||
"action": "Final Answer",
|
||||
"action_input": "Final response to human"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB```then Observation:.
|
||||
{{historic_messages}}
|
||||
Question: {{query}}
|
||||
*/
|
||||
const EN_Template = `
|
||||
Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools:
|
||||
Respond to the human as helpfully and accurately as possible.You have access to the following tools:
|
||||
|
||||
%s
|
||||
|
||||
Use the following format:
|
||||
Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).
|
||||
Valid "action" values: "Final Answer" or %s
|
||||
|
||||
Provide only ONE action per $JSON_BLOB, as shown:
|
||||
` + "```" + `
|
||||
{
|
||||
"action": $TOOL_NAME,
|
||||
"action_input": $ACTION_INPUT
|
||||
}
|
||||
` + "```" + `
|
||||
Follow this format:
|
||||
Question: %s
|
||||
Thought: %s
|
||||
Action: the action to take, should be one of %s
|
||||
Action Input: %s
|
||||
Observation: %s
|
||||
... (this Thought/Action/Action Input/Observation can repeat N times)
|
||||
Thought: %s
|
||||
Final Answer: %s
|
||||
Thought: %s
|
||||
Action: ` + "```" + `$JSON_BLOB` + "```" + `
|
||||
|
||||
Observation: %s
|
||||
... (repeat Thought/Action/Observation N times)
|
||||
Thought: %s
|
||||
Action:` + "```" + `
|
||||
{
|
||||
"action": "Final Answer",
|
||||
"action_input": "Final response to human"
|
||||
}
|
||||
` + "```" + `
|
||||
Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate.Format is Action:` + "```" + `$JSON_BLOB` + "```" + `then Observation:.
|
||||
%s
|
||||
|
||||
Question: %s
|
||||
`
|
||||
|
||||
/*
|
||||
尽你所能回答以下问题。你可以使用以下工具:
|
||||
尽可能帮助和准确地回答人的问题。您可以使用以下工具:
|
||||
|
||||
%s
|
||||
{tool_descs}
|
||||
|
||||
请使用以下格式,其中Action字段后必须跟着Action Input字段,并且不要将Action Input替换成Input或者tool等字段,不能出现格式以外的字段名,每个字段在每个轮次只出现一次:
|
||||
Question: 你需要回答的输入问题
|
||||
Thought: 你应该总是思考该做什么
|
||||
Action: 要采取的动作,动作只能是%s中的一个 ,一定不要加入其它内容
|
||||
Action Input: 行动的输入,必须出现在Action后。
|
||||
Observation: 行动的结果
|
||||
...(这个Thought/Action/Action Input/Observation可以重复N次)
|
||||
Thought: 我现在知道最终答案
|
||||
Final Answer: 对原始输入问题的最终答案
|
||||
使用 json blob,通过提供 action key(工具名称)和 action_input key(工具输入)来指定工具。
|
||||
有效的 "action"值为 "Final Answer"或 {tool_names}
|
||||
|
||||
再次重申,不要修改以上模板的字段名称,开始吧!
|
||||
每个 $JSON_BLOB 只能提供一个操作,如图所示:
|
||||
|
||||
Question: %s
|
||||
```
|
||||
|
||||
{{
|
||||
"action": $TOOL_NAME,
|
||||
"action_input": $ACTION_INPUT
|
||||
}}
|
||||
|
||||
```
|
||||
|
||||
按照以下格式:
|
||||
Question: 输入要回答的问题
|
||||
Thought: 考虑之前和之后的步骤
|
||||
Action:
|
||||
```
|
||||
$JSON_BLOB
|
||||
```
|
||||
|
||||
Observation: 行动结果
|
||||
...(这个Thought/Action//Observation可以重复N次)
|
||||
Thought: 我知道该回应什么
|
||||
Action:
|
||||
```
|
||||
|
||||
{{
|
||||
"action": "Final Answer",
|
||||
"action_input": "Final response to human"
|
||||
}}
|
||||
|
||||
```
|
||||
|
||||
开始!提醒您始终使用单个操作的有效 json blob 进行响应。必要时使用工具。如果合适,可直接响应。格式为 Action:```$JSON_BLOB```then Observation:.
|
||||
{historic_messages}
|
||||
Question: {input}
|
||||
*/
|
||||
const CH_Template = `
|
||||
尽你所能回答以下问题。你可以使用以下工具:
|
||||
尽可能帮助和准确地回答人的问题。您可以使用以下工具:
|
||||
|
||||
%s
|
||||
|
||||
请使用以下格式,其中Action字段后必须跟着Action Input字段,并且不要将Action Input替换成Input或者tool等字段,不能出现格式以外的字段名,每个字段在每个轮次只出现一次:
|
||||
使用 json blob,通过提供 action key(工具名称)和 action_input key(工具输入)来指定工具。
|
||||
有效的 "action"值为 "Final Answer"或 %s
|
||||
|
||||
每个 $JSON_BLOB 只能提供一个操作,如图所示:
|
||||
` + "```" + `
|
||||
{
|
||||
"action": $TOOL_NAME,
|
||||
"action_input": $ACTION_INPUT
|
||||
}
|
||||
` + "```" + `
|
||||
按照以下格式:
|
||||
Question: %s
|
||||
Thought: %s
|
||||
Action: 要采取的动作,动作只能是%s中的一个 ,一定不要加入其它内容
|
||||
Action Input: %s
|
||||
Action: ` + "```" + `$JSON_BLOB` + "```" + `
|
||||
|
||||
Observation: %s
|
||||
...(这个Thought/Action/Action Input/Observation可以重复N次)
|
||||
...(这个Thought/Action//Observation可以重复N次)
|
||||
Thought: %s
|
||||
Final Answer: %s
|
||||
|
||||
Action:` + "```" + `
|
||||
{
|
||||
"action": "Final Answer",
|
||||
"action_input": "Final response to human"
|
||||
}
|
||||
` + "```" + `
|
||||
开始!提醒您始终使用单个操作的有效 json blob 进行响应。必要时使用工具。如果合适,可直接响应。格式为 Action:` + "```" + `$JSON_BLOB` + "```" + `then Observation:.
|
||||
%s
|
||||
|
||||
Question: %s
|
||||
`
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
## 简介
|
||||
---
|
||||
title: AI 缓存
|
||||
keywords: [higress,ai cache]
|
||||
description: AI 缓存插件配置参考
|
||||
---
|
||||
|
||||
**Note**
|
||||
|
||||
> 需要数据面的proxy wasm版本大于等于0.2.100
|
||||
|
||||
> 编译时,需要带上版本的tag,例如:`tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer proxy_wasm_version_0_2_100" ./`
|
||||
## 功能说明
|
||||
|
||||
LLM 结果缓存插件,默认配置方式可以直接用于 openai 协议的结果缓存,同时支持流式和非流式响应的缓存。
|
||||
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`认证阶段`
|
||||
插件执行优先级:`10`
|
||||
|
||||
## 配置说明
|
||||
|
||||
| Name | Type | Requirement | Default | Description |
|
||||
|
||||
41
plugins/wasm-go/extensions/ai-cache/README_EN.md
Normal file
41
plugins/wasm-go/extensions/ai-cache/README_EN.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: AI Cache
|
||||
keywords: [higress,ai cache]
|
||||
description: AI Cache Plugin Configuration Reference
|
||||
---
|
||||
## Function Description
|
||||
LLM result caching plugin, the default configuration can be directly used for result caching under the OpenAI protocol, and it supports caching of both streaming and non-streaming responses.
|
||||
|
||||
## Runtime Properties
|
||||
Plugin Execution Phase: `Authentication Phase`
|
||||
Plugin Execution Priority: `10`
|
||||
|
||||
## Configuration Description
|
||||
| Name | Type | Requirement | Default | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| cacheKeyFrom.requestBody | string | optional | "messages.@reverse.0.content" | Extracts a string from the request Body based on [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) syntax |
|
||||
| cacheValueFrom.responseBody | string | optional | "choices.0.message.content" | Extracts a string from the response Body based on [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) syntax |
|
||||
| cacheStreamValueFrom.responseBody | string | optional | "choices.0.delta.content" | Extracts a string from the streaming response Body based on [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) syntax |
|
||||
| cacheKeyPrefix | string | optional | "higress-ai-cache:" | Prefix for the Redis cache key |
|
||||
| cacheTTL | integer | optional | 0 | Cache expiration time in seconds, default value is 0, which means never expire |
|
||||
| redis.serviceName | string | required | - | The complete FQDN name of the Redis service, including the service type, e.g., my-redis.dns, redis.my-ns.svc.cluster.local |
|
||||
| redis.servicePort | integer | optional | 6379 | Redis service port |
|
||||
| redis.timeout | integer | optional | 1000 | Timeout for requests to Redis, in milliseconds |
|
||||
| redis.username | string | optional | - | Username for logging into Redis |
|
||||
| redis.password | string | optional | - | Password for logging into Redis |
|
||||
| returnResponseTemplate | string | optional | `{"id":"from-cache","choices":[%s],"model":"gpt-4o","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}` | Template for returning HTTP response, with %s marking the part to be replaced by cache value |
|
||||
| returnStreamResponseTemplate | string | optional | `data:{"id":"from-cache","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"gpt-4o","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}\n\ndata:[DONE]\n\n` | Template for returning streaming HTTP response, with %s marking the part to be replaced by cache value |
|
||||
|
||||
## Configuration Example
|
||||
```yaml
|
||||
redis:
|
||||
serviceName: my-redis.dns
|
||||
timeout: 2000
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
The current default cache key is based on the GJSON PATH expression: `messages.@reverse.0.content`, meaning to get the content of the first item after reversing the messages array;
|
||||
GJSON PATH supports conditional syntax, for instance, if you want to take the content of the last role as user as the key, it can be written as: `messages.@reverse.#(role=="user").content`;
|
||||
If you want to concatenate all the content with role as user into an array as the key, it can be written as: `messages.@reverse.#(role=="user")#.content`;
|
||||
It also supports pipeline syntax, for example, if you want to take the second role as user as the key, it can be written as: `messages.@reverse.#(role=="user")#.content|1`.
|
||||
For more usage, you can refer to the [official documentation](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) and use the [GJSON Playground](https://gjson.dev/) for syntax testing.
|
||||
@@ -7,15 +7,17 @@ description: AI 历史对话插件配置参考
|
||||
## 功能说明
|
||||
|
||||
`AI 历史对话` 基于请求头实现用户身份识别,并自动缓存对应用户的历史对话,且在后续对话中自动填充到上下文。同时支持用户主动查询历史对话。
|
||||
|
||||
**Note**
|
||||
|
||||
> 需要数据面的proxy wasm版本大于等于0.2.100
|
||||
|
||||
> 编译时,需要带上版本的tag,例如:
|
||||
`tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer proxy_wasm_version_0_2_100" ./`
|
||||
|
||||
> 路径后缀匹配 `ai-history/query` 时,会返回历史对话
|
||||
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`默认阶段`
|
||||
插件执行优先级:`650`
|
||||
|
||||
|
||||
## 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | Description |
|
||||
@@ -169,4 +171,4 @@ curl 'http://example.com/api/openai/v1/chat/completions/ai-history/query?cnt=3'
|
||||
]
|
||||
```
|
||||
|
||||
返回三个历史对话,如果未传入 cnt 默认返回所有缓存历史对话。
|
||||
返回三个历史对话,如果未传入 cnt 默认返回所有缓存历史对话。
|
||||
|
||||
158
plugins/wasm-go/extensions/ai-history/README_EN.md
Normal file
158
plugins/wasm-go/extensions/ai-history/README_EN.md
Normal file
@@ -0,0 +1,158 @@
|
||||
---
|
||||
title: AI History Dialogue
|
||||
keywords: [ AI Gateway, AI History Dialogue ]
|
||||
description: AI History Dialogue Plugin Configuration Reference
|
||||
---
|
||||
## Functional Description
|
||||
`AI History Dialogue` implements user identity recognition based on request headers and automatically caches the historical dialogues of corresponding users, which are then automatically filled into the context in subsequent dialogues. It also supports users to actively query historical dialogues.
|
||||
|
||||
**Note**
|
||||
|
||||
> When the path suffix matches `ai-history/query`, it will return the historical dialogues.
|
||||
|
||||
## Runtime Properties
|
||||
Plugin Execution Phase: `Default Phase`
|
||||
Plugin Execution Priority: `650`
|
||||
|
||||
## Configuration Fields
|
||||
| Name | Data Type | Required | Default Value | Description |
|
||||
|-------------------|---------|----------|-----------------------|---------------------------------------------------------------------------|
|
||||
| identityHeader | string | optional | "Authorization" | The request header for identity resolution, can be Authorization, X-Mse-Consumer, etc. |
|
||||
| fillHistoryCnt | integer | optional | 3 | Default number of historical dialogues to be filled. |
|
||||
| cacheKeyPrefix | string | optional | "higress-ai-history:" | Prefix for Redis cache key. |
|
||||
| cacheTTL | integer | optional | 0 | Cache expiration time in seconds, default value is 0, meaning it never expires. |
|
||||
| redis.serviceName | string | required | - | Redis service name, full FQDN name with service type, e.g., my-redis.dns, redis.my-ns.svc.cluster.local |
|
||||
| redis.servicePort | integer | optional | 6379 | Redis service port. |
|
||||
| redis.timeout | integer | optional | 1000 | Timeout for requests to Redis, in milliseconds. |
|
||||
| redis.username | string | optional | - | Username for logging into Redis. |
|
||||
| redis.password | string | optional | - | Password for logging into Redis. |
|
||||
|
||||
## Usage Example
|
||||
### Configuration Information
|
||||
```yaml
|
||||
redis:
|
||||
serviceName: my-redis.dns
|
||||
timeout: 2000
|
||||
```
|
||||
|
||||
### Request Example
|
||||
**Auto-fill Request Example:**
|
||||
|
||||
First Round Request:
|
||||
```
|
||||
curl 'http://example.com/api/openai/v1/chat/completions?fill_history_cnt=3' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer sk-Nzf7RtkdS4s0zFyn5575124129254d9bAf9473A5D7D06dD3'
|
||||
--data-raw '{"model":"qwen-long","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Can Higress replace Nginx?"
|
||||
}
|
||||
],"presence_penalty":0,"temperature":0.7,"top_p":0.95}'
|
||||
```
|
||||
After Request Fill:
|
||||
> First round request, no fill. Consistent with the original request.
|
||||
|
||||
First Round Response:
|
||||
```json
|
||||
{
|
||||
"id": "02f4c621-820e-97d4-a905-1e3d0d8f59c6",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "While both Higress and Nginx have gateway functionalities, their design philosophies and application scenarios differ. Nginx is better known as a high-performance HTTP and reverse proxy server, while Higress is a cloud-native gateway that integrates many cloud-native features such as service mesh, observability, and security management in addition to basic routing capabilities.\n\nTherefore, if you want to deploy applications in a cloud-native environment and wish to obtain advanced features required for modern applications, such as service governance, gray release, circuit breaker and rate limiting, and security authentication, then Higress can be a good alternative to Nginx. However, if it's a relatively simple static website or only requires basic reverse proxy functionality, traditional Nginx configurations may be simpler and more direct."
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"created": 1724077770,
|
||||
"model": "qwen-long",
|
||||
"object": "chat.completion",
|
||||
"usage": {
|
||||
"prompt_tokens": 7316,
|
||||
"completion_tokens": 164,
|
||||
"total_tokens": 7480
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Second Round Request:
|
||||
```
|
||||
curl 'http://example.com/api/openai/v1/chat/completions?fill_history_cnt=3' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer sk-Nzf7RtkdS4s0zFyn5575124129254d9bAf9473A5D7D06dD3'
|
||||
--data-raw '{"model":"qwen-long","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What about Spring Cloud GateWay?"
|
||||
}
|
||||
],"presence_penalty":0,"temperature":0.7,"top_p":0.95}'
|
||||
```
|
||||
After Request Fill:
|
||||
> Second round request, automatically filled with the historical dialogue from the previous round.
|
||||
```
|
||||
curl 'http://example.com/api/openai/v1/chat/completions?fill_history_cnt=3' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer sk-Nzf7RtkdS4s0zFyn5575124129254d9bAf9473A5D7D06dD3'
|
||||
--data-raw '{"model":"qwen-long","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Can Higress replace Nginx?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "While both Higress and Nginx have gateway functionalities, their design philosophies and application scenarios differ. Nginx is better known as a high-performance HTTP and reverse proxy server, while Higress is a cloud-native gateway that integrates many cloud-native features such as service mesh, observability, and security management in addition to basic routing capabilities.\n\nTherefore, if you want to deploy applications in a cloud-native environment and wish to obtain advanced features required for modern applications, such as service governance, gray release, circuit breaker and rate limiting, and security authentication, then Higress can be a good alternative to Nginx. However, if it's a relatively simple static website or only requires basic reverse proxy functionality, traditional Nginx configurations may be simpler and more direct."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What about Spring Cloud GateWay?"
|
||||
}
|
||||
],"presence_penalty":0,"temperature":0.7,"top_p":0.95}'
|
||||
```
|
||||
|
||||
Each round of requests only needs to include the current question and the number of historical dialogues to fill in, enabling automatic filling of historical dialogues.
|
||||
|
||||
**Example of Retrieving Historical Data:**
|
||||
```
|
||||
curl 'http://example.com/api/openai/v1/chat/completions/ai-history/query?cnt=3' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer sk-Nzf7RtkdS4s0zFyn5575124129254d9bAf9473A5D7D06dD3'
|
||||
```
|
||||
|
||||
Response Example:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Can Higress replace Nginx?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "While both Higress and Nginx have gateway functionalities, their design philosophies and application scenarios differ. Nginx is better known as a high-performance HTTP and reverse proxy server, while Higress is a cloud-native gateway that integrates many cloud-native features such as service mesh, observability, and security management in addition to basic routing capabilities.\\n\\nTherefore, if you want to deploy applications in a cloud-native environment and wish to obtain advanced features required for modern applications, such as service governance, gray release, circuit breaker and rate limiting, and security authentication, then Higress can be a good alternative to Nginx. However, if it's a relatively simple static website or only requires basic reverse proxy functionality, traditional Nginx configurations may be simpler and more direct."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What about Spring Cloud GateWay?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Compared to Spring Cloud Gateway, Higress is also an API gateway, but there are some key differences between them:\\n\\n- **Design Philosophy**: Spring Cloud Gateway mainly targets service-to-service communication and routing in microservice architectures. As part of the Spring Cloud ecosystem, it is more focused on scenarios for Java developers in microservices. In contrast, Higress, as a cloud-native gateway, not only focuses on service communication but also offers a range of cloud-native features such as service mesh, observability, security management, etc.\\n- **Deployment Method**: Spring Cloud Gateway typically runs as part of microservice applications on application servers, whereas Higress usually deploys as an independent microservice or containerized service in Kubernetes environments, suitable for modern cloud-native deployment models.\\n- **Scalability and Integration**: Higress provides wider integration and support, for example, deep integration with ecosystems like Istio, Kubernetes, etc., making it better suited for complex cloud-native environments.\\n\\nTherefore, if your application is built on Spring Cloud and you want a lightweight, easy-to-integrate service gateway, then Spring Cloud Gateway might be a suitable choice. However, if you are building or refactoring cloud-native applications and require more powerful routing rules, service governance, observability, etc., then Higress is likely a better choice."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Can Higress replace Nginx?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "While both Higress and Nginx have gateway functionalities, their design philosophies and application scenarios differ. Nginx is better known as a high-performance HTTP and reverse proxy server, while Higress is a cloud-native gateway that integrates many cloud-native features such as service mesh, observability, and security management in addition to basic routing capabilities.\\n\\nTherefore, if you want to deploy applications in a cloud-native environment and wish to obtain advanced features required for modern applications, such as service governance, gray release, circuit breaker and rate limiting, and security authentication, then Higress can be a good alternative to Nginx. However, if it's a relatively simple static website or only requires basic reverse proxy functionality, traditional Nginx configurations may be simpler and more direct."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Returns three historical dialogues. If the `cnt` parameter is not provided, it will default to returning all cached historical dialogues.
|
||||
@@ -1,15 +1,20 @@
|
||||
## 简介
|
||||
---
|
||||
title: AI 意图识别
|
||||
keywords: [ AI网关, AI意图识别 ]
|
||||
description: AI 意图识别插件配置参考
|
||||
---
|
||||
|
||||
**Note**
|
||||
|
||||
> 需要数据面的proxy wasm版本大于等于0.2.100
|
||||
|
||||
> 编译时,需要带上版本的tag,例如:`tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer proxy_wasm_version_0_2_100" ./`
|
||||
## 功能说明
|
||||
|
||||
LLM 意图识别插件,能够智能判断用户请求与某个领域或agent的功能契合度,从而提升不同模型的应用效果和用户体验
|
||||
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`默认阶段`
|
||||
插件执行优先级:`700`
|
||||
|
||||
## 配置说明
|
||||
> 1.该插件的优先级要高于ai-cache、ai-proxy等后续使用意图的插件,后续插件可以通过proxywasm.GetProperty([]string{"intent_category"})方法获取到意图主题,按照意图主题去做不同缓存库或者大模型的选择
|
||||
> 1.该插件的优先级高于ai-proxy等后续使用意图的插件,后续插件可以通过proxywasm.GetProperty([]string{"intent_category"})方法获取到意图主题,按照意图主题去做不同缓存库或者大模型的选择
|
||||
|
||||
> 2.需新建一条higress的大模型路由,供该插件访问大模型,如:路由以 /intent 作为前缀,服务选择大模型服务,为该路由开启ai-proxy插件
|
||||
|
||||
@@ -19,7 +24,7 @@ LLM 意图识别插件,能够智能判断用户请求与某个领域或agent
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------------- | --------------- | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `scene.category` | string | 必填 | - | 预设场景类别,以"|"分割,如:"金融|电商|法律|Higress"|
|
||||
| `scene.category` | string | 必填 | - | 预设场景类别,以`|`分割,如:`金融|电商|法律|Higress`|
|
||||
| `scene.prompt` | string | 非必填 | 你是一个智能类别识别助手,负责根据用户提出的问题和预设的类别,确定问题属于哪个预设的类别,并给出相应的类别。用户提出的问题为:%s,预设的类别为%s,直接返回一种具体类别,如果没有找到就返回'NotFound'。 | llm请求prompt模板 |
|
||||
| `llm.proxyServiceName` | string | 必填 | - | 新建的higress服务,指向大模型 (取higress中的 FQDN 值)|
|
||||
| `llm.proxyUrl` | string | 必填 | - | 大模型路由请求地址全路径,可以是网关自身的地址,也可以是其他大模型的地址(openai协议),例如:http://127.0.0.1:80/intent/compatible-mode/v1/chat/completions |
|
||||
|
||||
45
plugins/wasm-go/extensions/ai-intent/README_EN.md
Normal file
45
plugins/wasm-go/extensions/ai-intent/README_EN.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: AI Intent Recognition
|
||||
keywords: [ AI Gateway, AI Intent Recognition ]
|
||||
description: AI Intent Recognition Plugin Configuration Reference
|
||||
---
|
||||
## Function Description
|
||||
LLM Intent Recognition plugin can intelligently determine the alignment between user requests and the functionalities of a certain domain or agent, thereby enhancing the application effectiveness of different models and user experience.
|
||||
|
||||
## Execution Attributes
|
||||
Plugin execution phase: `Default Phase`
|
||||
|
||||
Plugin execution priority: `700`
|
||||
|
||||
## Configuration Instructions
|
||||
> 1. This plugin's priority is higher than that of plugins such as ai-proxy which follow up and use intent. Subsequent plugins can retrieve the intent category using the proxywasm.GetProperty([]string{"intent_category"}) method and make selections for different cache libraries or large models based on the intent category.
|
||||
> 2. A new Higress large model route needs to be created to allow this plugin to access the large model. For example: the route should use `/intent` as a prefix, the service should select the large model service, and the ai-proxy plugin should be enabled for this route.
|
||||
> 3. A fixed-address service needs to be created (for example, intent-service), which points to 127.0.0.1:80 (i.e., the gateway instance and port). The ai-intent plugin requires this service for calling to access the newly added route. The service name corresponds to llm.proxyServiceName (a DNS type service can also be created to allow the plugin to access other large models).
|
||||
> 4. If using a fixed-address service to call the gateway itself, 127.0.0.1 must be added to the gateway's access whitelist.
|
||||
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
| -------------- | --------------- | ----------- | ------------- | --------------------------------------------------------------- |
|
||||
| `scene.category` | string | Required | - | Preset scene categories, separated by "|", e.g.: "Finance|E-commerce|Law|Higress" |
|
||||
| `scene.prompt` | string | Optional | You are a smart category recognition assistant responsible for determining which preset category a user’s question belongs to based on the question posed by the user and the preset categories, and returning the corresponding category. The user's question is: %s, the preset categories are %s, directly return a specific category; if not found, return 'NotFound'. | llm request prompt template |
|
||||
| `llm.proxyServiceName` | string | Required | - | Newly created Higress service pointing to the large model (use the FQDN value from Higress) |
|
||||
| `llm.proxyUrl` | string | Required | - | The full path to the large model route request address, which can be the gateway’s own address or the address of another large model (OpenAI protocol), for example: http://127.0.0.1:80/intent/compatible-mode/v1/chat/completions |
|
||||
| `llm.proxyDomain` | string | Optional | Retrieved from proxyUrl | Domain of the large model service |
|
||||
| `llm.proxyPort` | string | Optional | Retrieved from proxyUrl | Port number of the large model service |
|
||||
| `llm.proxyApiKey` | string | Optional | - | API_KEY corresponding to the external large model service when using it |
|
||||
| `llm.proxyModel` | string | Optional | qwen-long | Type of the large model |
|
||||
| `llm.proxyTimeout` | number | Optional | 10000 | Timeout for calling the large model, unit ms, default: 10000ms |
|
||||
|
||||
## Configuration Example
|
||||
```yaml
|
||||
scene:
|
||||
category: "Finance|E-commerce|Law|Higress"
|
||||
prompt: "You are a smart category recognition assistant responsible for determining which preset category a user's question belongs to based on the question posed by the user and the preset categories, and returning the corresponding category. The user's question is: '%s', the preset categories are '%s', directly return a specific category; if not found, return 'NotFound'."
|
||||
llm:
|
||||
proxyServiceName: "intent-service.static"
|
||||
proxyUrl: "http://127.0.0.1:80/intent/compatible-mode/v1/chat/completions"
|
||||
proxyDomain: "127.0.0.1"
|
||||
proxyPort: "80"
|
||||
proxyModel: "qwen-long"
|
||||
proxyApiKey: ""
|
||||
proxyTimeout: "10000"
|
||||
```
|
||||
@@ -1,15 +1,17 @@
|
||||
## 简介
|
||||
|
||||
**Note**
|
||||
|
||||
> 需要数据面的proxy wasm版本大于等于0.2.100
|
||||
>
|
||||
|
||||
> 编译时,需要带上版本的tag,例如:tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer proxy_wasm_version_0_2_100" ./
|
||||
---
|
||||
title: AI JSON 格式化
|
||||
keywords: [ AI网关, AI JSON 格式化 ]
|
||||
description: AI JSON 格式化插件配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
|
||||
LLM响应结构化插件,用于根据默认或用户配置的Json Schema对AI的响应进行结构化,以便后续插件处理。注意目前只支持 `非流式响应`。
|
||||
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`默认阶段`
|
||||
插件执行优先级:`150`
|
||||
|
||||
### 配置说明
|
||||
|
||||
@@ -109,7 +111,6 @@ curl -X POST "http://localhost:8001/v1/chat/completions" \
|
||||
### 支持openai接口的AI服务
|
||||
以qwen为例,基本配置如下:
|
||||
|
||||
Yaml格式配置如下
|
||||
```yaml
|
||||
serviceName: qwen
|
||||
serviceDomain: dashscope.aliyuncs.com
|
||||
@@ -133,70 +134,15 @@ jsonSchema:
|
||||
additionalProperties: false
|
||||
```
|
||||
|
||||
JSON 格式配置
|
||||
```json
|
||||
{
|
||||
"serviceName": "qwen",
|
||||
"serviceUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"apiKey": "[Your API Key]",
|
||||
"jsonSchema": {
|
||||
"title": "ActionItemsSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action_items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Description of the action item."
|
||||
},
|
||||
"due_date": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Due date for the action item, can be null if not specified."
|
||||
},
|
||||
"owner": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Owner responsible for the action item, can be null if not specified."
|
||||
}
|
||||
},
|
||||
"required": ["description", "due_date", "owner"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"description": "List of action items from the meeting."
|
||||
}
|
||||
},
|
||||
"required": ["action_items"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 本地网关服务
|
||||
为了能复用已经配置好的服务,本插件也支持配置本地网关服务。例如,若网关已经配置好了[AI-proxy服务](../ai-proxy/README.md),则可以直接配置如下:
|
||||
1. 创建一个固定IP为127.0.0.1的服务,例如localservice.static
|
||||
```yaml
|
||||
- name: outbound|10000||localservice.static
|
||||
connect_timeout: 30s
|
||||
type: LOGICAL_DNS
|
||||
dns_lookup_family: V4_ONLY
|
||||
lb_policy: ROUND_ROBIN
|
||||
load_assignment:
|
||||
cluster_name: outbound|8001||localservice.static
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: 127.0.0.1
|
||||
port_value: 10000
|
||||
```
|
||||
为了能复用已经配置好的服务,本插件也支持配置本地网关服务。例如,若网关已经配置好了AI-proxy服务,则可以直接配置如下:
|
||||
1. 创建一个固定IP地址为127.0.0.1:80的服务,例如localservice.static
|
||||
|
||||
2. 配置文件中添加localservice.static的服务配置
|
||||
```yaml
|
||||
serviceName: localservice
|
||||
serviceDomain: 127.0.0.1
|
||||
servicePort: 10000
|
||||
servicePort: 80
|
||||
```
|
||||
3. 自动提取请求的Path,Header等信息
|
||||
插件会自动提取请求的Path,Header等信息,从而避免对AI服务的重复配置。
|
||||
|
||||
140
plugins/wasm-go/extensions/ai-json-resp/README_EN.md
Normal file
140
plugins/wasm-go/extensions/ai-json-resp/README_EN.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
title: AI JSON Formatting
|
||||
keywords: [ AI Gateway, AI JSON Formatting ]
|
||||
description: AI JSON Formatting plugin configuration reference
|
||||
---
|
||||
## Function Description
|
||||
LLM structured response plugin, used to structure AI responses according to the default or user-configured Json Schema for subsequent plugin processing. Note that only `non-streaming responses` are currently supported.
|
||||
|
||||
## Running Attributes
|
||||
Plugin execution phase: `default phase`
|
||||
Plugin execution priority: `150`
|
||||
|
||||
### Configuration Description
|
||||
| Name | Type | Requirement | Default | **Description** |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| serviceName | str | required | - | AI service or gateway service name that supports AI-Proxy |
|
||||
| serviceDomain | str | optional | - | AI service or gateway service domain/IP address that supports AI-Proxy |
|
||||
| servicePath | str | optional | '/v1/chat/completions' | AI service or gateway service base path that supports AI-Proxy |
|
||||
| serviceUrl | str | optional | - | AI service or gateway service URL that supports AI-Proxy; the plugin will automatically extract domain and path to fill in the unconfigured serviceDomain or servicePath |
|
||||
| servicePort | int | optional | 443 | Gateway service port |
|
||||
| serviceTimeout | int | optional | 50000 | Default request timeout |
|
||||
| maxRetry | int | optional | 3 | Number of retry attempts when the answer cannot be correctly extracted and formatted |
|
||||
| contentPath | str | optional | "choices.0.message.content” | gpath path to extract the response result from the LLM answer |
|
||||
| jsonSchema | str (json) | optional | - | The jsonSchema against which the request is validated; if empty, only valid Json format responses are returned |
|
||||
| enableSwagger | bool | optional | false | Whether to enable the Swagger protocol for validation |
|
||||
| enableOas3 | bool | optional | true | Whether to enable the Oas3 protocol for validation |
|
||||
| enableContentDisposition | bool | optional | true | Whether to enable the Content-Disposition header; if enabled, the response header will include `Content-Disposition: attachment; filename="response.json"` |
|
||||
|
||||
> For performance reasons, the maximum supported Json Schema depth is 6 by default. Json Schemas exceeding this depth will not be used to validate responses; the plugin will only check if the returned response is a valid Json format.
|
||||
|
||||
### Request and Return Parameter Description
|
||||
- **Request Parameters**: The request format for this plugin is the OpenAI request format, including the `model` and `messages` fields, where `model` is the AI model name and `messages` is a list of conversation messages, each containing `role` and `content` fields, with `role` being the message role and `content` being the message content.
|
||||
```json
|
||||
{
|
||||
"model": "gpt-4",
|
||||
"messages": [
|
||||
{"role": "user", "content": "give me a api doc for add the variable x to x+5"}
|
||||
]
|
||||
}
|
||||
```
|
||||
Other request parameters should refer to the corresponding documentation of the configured AI service or gateway service.
|
||||
|
||||
- **Return Parameters**:
|
||||
- Returns a `Json format response` that satisfies the constraints of the defined Json Schema.
|
||||
- If no Json Schema is defined, returns a valid `Json format response`.
|
||||
- If an internal error occurs, returns `{ "Code": 10XX, "Msg": "Error message" }`.
|
||||
|
||||
## Request Example
|
||||
```bash
|
||||
curl -X POST "http://localhost:8001/v1/chat/completions" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "gpt-4",
|
||||
"messages": [
|
||||
{"role": "user", "content": "give me a api doc for add the variable x to x+5"}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
## Return Example
|
||||
### Normal Return
|
||||
Under normal circumstances, the system should return JSON data validated by the JSON Schema. If no JSON Schema is configured, the system will return legally valid JSON data that complies with JSON standards.
|
||||
```json
|
||||
{
|
||||
"apiVersion": "1.0",
|
||||
"request": {
|
||||
"endpoint": "/add_to_five",
|
||||
"method": "POST",
|
||||
"port": 8080,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"x": 7
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Exception Return
|
||||
In case of an error, the return status code is `500`, and the return content is a JSON format error message. It contains two fields: error code `Code` and error message `Msg`.
|
||||
```json
|
||||
{
|
||||
"Code": 1006,
|
||||
"Msg": "retry count exceed max retry count"
|
||||
}
|
||||
```
|
||||
|
||||
### Error Code Description
|
||||
| Error Code | Description |
|
||||
| --- | --- |
|
||||
| 1001 | The configured Json Schema is not in a valid Json format |
|
||||
| 1002 | The configured Json Schema compilation failed; it is not a valid Json Schema format or depth exceeds jsonSchemaMaxDepth while rejectOnDepthExceeded is true |
|
||||
| 1003 | Unable to extract valid Json from the response |
|
||||
| 1004 | Response is an empty string |
|
||||
| 1005 | Response does not conform to the Json Schema definition |
|
||||
| 1006 | Retry count exceeds the maximum limit |
|
||||
| 1007 | Unable to retrieve the response content; may be due to upstream service configuration errors or incorrect ContentPath path to get the content |
|
||||
| 1008 | serviceDomain is empty; please note that either serviceDomain or serviceUrl cannot be empty at the same time |
|
||||
|
||||
## Service Configuration Description
|
||||
This plugin requires configuration of upstream services to support automatic retry mechanisms in case of exceptions. Supported configurations mainly include `AI services supporting OpenAI interfaces` or `local gateway services`.
|
||||
|
||||
### AI Services Supporting OpenAI Interfaces
|
||||
Taking Qwen as an example, the basic configuration is as follows:
|
||||
```yaml
|
||||
serviceName: qwen
|
||||
serviceDomain: dashscope.aliyuncs.com
|
||||
apiKey: [Your API Key]
|
||||
servicePath: /compatible-mode/v1/chat/completions
|
||||
jsonSchema:
|
||||
title: ReasoningSchema
|
||||
type: object
|
||||
properties:
|
||||
reasoning_steps:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: The reasoning steps leading to the final conclusion.
|
||||
answer:
|
||||
type: string
|
||||
description: The final answer, taking into account the reasoning steps.
|
||||
required:
|
||||
- reasoning_steps
|
||||
- answer
|
||||
additionalProperties: false
|
||||
```
|
||||
|
||||
### Local Gateway Services
|
||||
To reuse already configured services, this plugin also supports configuring local gateway services. For example, if the gateway has already configured the AI-proxy service, it can be directly configured as follows:
|
||||
|
||||
1. Create a service with a fixed IP address of 127.0.0.1:80, for example, localservice.static.
|
||||
2. Add the service configuration for localservice.static in the configuration file.
|
||||
```yaml
|
||||
serviceName: localservice
|
||||
serviceDomain: 127.0.0.1
|
||||
servicePort: 80
|
||||
```
|
||||
3. Automatically extract request Path, Header, and other information.
|
||||
The plugin will automatically extract request Path, Header, and other information to avoid repetitive configuration for the AI service.
|
||||
@@ -1,7 +1,19 @@
|
||||
# 简介
|
||||
AI提示词装饰器插件,支持在LLM的请求前后插入prompt。
|
||||
---
|
||||
title: AI 提示词
|
||||
keywords: [ AI网关, AI提示词 ]
|
||||
description: AI 提示词插件配置参考
|
||||
---
|
||||
|
||||
# 配置说明
|
||||
## 功能说明
|
||||
|
||||
AI提示词插件,支持在LLM的请求前后插入prompt。
|
||||
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`默认阶段`
|
||||
插件执行优先级:`450`
|
||||
|
||||
## 配置说明
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|----------------|-----------------|------|-----|----------------------------------|
|
||||
@@ -15,7 +27,7 @@ message object 配置说明:
|
||||
| `role` | string | 必填 | - | 角色 |
|
||||
| `content` | string | 必填 | - | 消息 |
|
||||
|
||||
# 示例
|
||||
## 示例
|
||||
|
||||
配置示例如下:
|
||||
|
||||
@@ -69,7 +81,7 @@ curl http://localhost/test \
|
||||
```
|
||||
|
||||
|
||||
# 基于geo-ip插件的能力,扩展AI提示词装饰器插件携带用户地理位置信息
|
||||
## 基于geo-ip插件的能力,扩展AI提示词装饰器插件携带用户地理位置信息
|
||||
如果需要在LLM的请求前后加入用户地理位置信息,请确保同时开启geo-ip插件和AI提示词装饰器插件。并且在相同的请求处理阶段里,geo-ip插件的优先级必须高于AI提示词装饰器插件。首先geo-ip插件会根据用户ip计算出用户的地理位置信息,然后通过请求属性传递给后续插件。比如在默认阶段里,geo-ip插件的priority配置1000,ai-prompt-decorator插件的priority配置500。
|
||||
|
||||
geo-ip插件配置示例:
|
||||
|
||||
130
plugins/wasm-go/extensions/ai-prompt-decorator/README_EN.md
Normal file
130
plugins/wasm-go/extensions/ai-prompt-decorator/README_EN.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: AI Prompts
|
||||
keywords: [ AI Gateway, AI Prompts ]
|
||||
description: AI Prompts plugin configuration reference
|
||||
---
|
||||
## Function Description
|
||||
The AI Prompts plugin allows inserting prompts before and after requests in LLM.
|
||||
|
||||
## Execution Properties
|
||||
Plugin execution phase: `Default Phase`
|
||||
Plugin execution priority: `450`
|
||||
|
||||
## Configuration Description
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|---------------|----------------------|-------------|---------------|--------------------------------------|
|
||||
| `prepend` | array of message object | optional | - | Statements inserted before the initial input |
|
||||
| `append` | array of message object | optional | - | Statements inserted after the initial input |
|
||||
|
||||
Message object configuration description:
|
||||
| Name | Data Type | Requirement | Default Value | Description |
|
||||
|-----------|-------------|-------------|---------------|-------------|
|
||||
| `role` | string | required | - | Role |
|
||||
| `content` | string | required | - | Message |
|
||||
|
||||
## Example
|
||||
An example configuration is as follows:
|
||||
```yaml
|
||||
prepend:
|
||||
- role: system
|
||||
content: "Please answer the questions in English."
|
||||
append:
|
||||
- role: user
|
||||
content: "After answering each question, try to ask a follow-up question."
|
||||
```
|
||||
|
||||
Using the above configuration to initiate a request:
|
||||
```bash
|
||||
curl http://localhost/test \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Who are you?"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
After processing through the plugin, the actual request will be:
|
||||
```bash
|
||||
curl http://localhost/test \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "Please answer the questions in English."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Who are you?"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "After answering each question, try to ask a follow-up question."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Based on the geo-ip plugin's capabilities, extend AI Prompt Decorator plugin to carry user geographic location information.
|
||||
If you need to include user geographic location information before and after the LLM's requests, please ensure both the geo-ip plugin and the AI Prompt Decorator plugin are enabled. Moreover, in the same request processing phase, the geo-ip plugin's priority must be higher than that of the AI Prompt Decorator plugin. First, the geo-ip plugin will calculate the user's geographic location information based on the user's IP, and then pass it to subsequent plugins via request attributes. For instance, in the default phase, the geo-ip plugin's priority configuration is 1000, while the ai-prompt-decorator plugin's priority configuration is 500.
|
||||
|
||||
Example configuration for the geo-ip plugin:
|
||||
```yaml
|
||||
ipProtocal: "ipv4"
|
||||
```
|
||||
|
||||
An example configuration for the AI Prompt Decorator plugin is as follows:
|
||||
```yaml
|
||||
prepend:
|
||||
- role: system
|
||||
content: "The user's current geographic location is, country: ${geo-country}, province: ${geo-province}, city: ${geo-city}."
|
||||
append:
|
||||
- role: user
|
||||
content: "After answering each question, try to ask a follow-up question."
|
||||
```
|
||||
|
||||
Using the above configuration to initiate a request:
|
||||
```bash
|
||||
curl http://localhost/test \
|
||||
-H "content-type: application/json" \
|
||||
-H "x-forwarded-for: 87.254.207.100,4.5.6.7" \
|
||||
-d '{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "How is the weather today?"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
After processing through the plugin, the actual request will be:
|
||||
```bash
|
||||
curl http://localhost/test \
|
||||
-H "content-type: application/json" \
|
||||
-H "x-forwarded-for: 87.254.207.100,4.5.6.7" \
|
||||
-d '{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "The user's current geographic location is, country: China, province: Beijing, city: Beijing."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "How is the weather today?"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "After answering each question, try to ask a follow-up question."
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
@@ -1,7 +1,19 @@
|
||||
# 简介
|
||||
---
|
||||
title: AI 提示词模版
|
||||
keywords: [ AI网关, AI提示词模版 ]
|
||||
description: AI 提示词模版配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
|
||||
AI提示词模板,用于快速构建同类型的AI请求。
|
||||
|
||||
# 配置说明
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`默认阶段`
|
||||
插件执行优先级:`500`
|
||||
|
||||
## 配置说明
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|----------------|-----------------|------|-----|----------------------------------|
|
||||
| `templates` | array of object | 必填 | - | 模板设置 |
|
||||
@@ -45,4 +57,4 @@ templates:
|
||||
"language": "python"
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
53
plugins/wasm-go/extensions/ai-prompt-template/README_EN.md
Normal file
53
plugins/wasm-go/extensions/ai-prompt-template/README_EN.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: AI Prompt Template
|
||||
keywords: [ AI Gateway, AI Prompt Template ]
|
||||
description: AI Prompt Template Configuration Reference
|
||||
---
|
||||
## Function Description
|
||||
AI prompt templates are used to quickly build similar types of AI requests.
|
||||
|
||||
## Execution Properties
|
||||
Plugin Execution Phase: `Default Phase`
|
||||
Plugin Execution Priority: `500`
|
||||
|
||||
## Configuration Description
|
||||
| Name | Data Type | Required | Default Value | Description |
|
||||
|-----------------|-------------------|----------|---------------|-----------------------------------|
|
||||
| `templates` | array of object | Required | - | Template settings |
|
||||
|
||||
Template object configuration description:
|
||||
| Name | Data Type | Required | Default Value | Description |
|
||||
|-----------------------|-------------------|----------|---------------|-----------------------------------|
|
||||
| `name` | string | Required | - | Template name |
|
||||
| `template.model` | string | Required | - | Model name |
|
||||
| `template.messages` | array of object | Required | - | Input for large model |
|
||||
|
||||
Message object configuration description:
|
||||
| Name | Data Type | Required | Default Value | Description |
|
||||
|----------------|-------------------|----------|---------------|-----------------------------------|
|
||||
| `role` | string | Required | - | Role |
|
||||
| `content` | string | Required | - | Message |
|
||||
|
||||
Configuration example:
|
||||
```yaml
|
||||
templates:
|
||||
- name: "developer-chat"
|
||||
template:
|
||||
model: gpt-3.5-turbo
|
||||
messages:
|
||||
- role: system
|
||||
content: "You are a {{program}} expert, in {{language}} programming language."
|
||||
- role: user
|
||||
content: "Write me a {{program}} program."
|
||||
```
|
||||
|
||||
Example request body using the above configuration:
|
||||
```json
|
||||
{
|
||||
"template": "developer-chat",
|
||||
"properties": {
|
||||
"program": "quick sort",
|
||||
"language": "python"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -15,6 +15,11 @@ description: AI 代理插件配置参考
|
||||
|
||||
> 请求路径后缀匹配 `/v1/embeddings` 时,对应文本向量场景,会用 OpenAI 的文本向量协议解析请求 Body,再转换为对应 LLM 厂商的文本向量协议
|
||||
|
||||
## 运行属性
|
||||
|
||||
插件执行阶段:`默认阶段`
|
||||
插件执行优先级:`100`
|
||||
|
||||
|
||||
## 配置字段
|
||||
|
||||
@@ -138,6 +143,10 @@ Groq 所对应的 `type` 为 `groq`。它并无特有的配置字段。
|
||||
|
||||
360智脑所对应的 `type` 为 `ai360`。它并无特有的配置字段。
|
||||
|
||||
#### GitHub模型
|
||||
|
||||
GitHub模型所对应的 `type` 为 `github`。它并无特有的配置字段。
|
||||
|
||||
#### Mistral
|
||||
|
||||
Mistral 所对应的 `type` 为 `mistral`。它并无特有的配置字段。
|
||||
@@ -210,6 +219,10 @@ DeepL 所对应的 `type` 为 `deepl`。它特有的配置字段如下:
|
||||
| ------------ | -------- | -------- | ------ | ---------------------------- |
|
||||
| `targetLang` | string | 必填 | - | DeepL 翻译服务需要的目标语种 |
|
||||
|
||||
#### Cohere
|
||||
|
||||
Cohere 所对应的 `type` 为 `cohere`。它并无特有的配置字段。
|
||||
|
||||
## 用法示例
|
||||
|
||||
### 使用 OpenAI 协议代理 Azure OpenAI 服务
|
||||
@@ -597,6 +610,65 @@ provider:
|
||||
}
|
||||
```
|
||||
|
||||
### 使用original协议代理百炼智能体应用
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: qwen
|
||||
apiTokens:
|
||||
- "YOUR_DASHSCOPE_API_TOKEN"
|
||||
protocol: original
|
||||
```
|
||||
|
||||
**请求实例**
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"prompt": "介绍一下Dubbo"
|
||||
},
|
||||
"parameters": {},
|
||||
"debug": {}
|
||||
}
|
||||
```
|
||||
|
||||
**响应实例**
|
||||
|
||||
```json
|
||||
{
|
||||
"output": {
|
||||
"finish_reason": "stop",
|
||||
"session_id": "677e7e8fbb874e1b84792b65042e1599",
|
||||
"text": "Apache Dubbo 是一个..."
|
||||
},
|
||||
"usage": {
|
||||
"models": [
|
||||
{
|
||||
"output_tokens": 449,
|
||||
"model_id": "qwen-max",
|
||||
"input_tokens": 282
|
||||
}
|
||||
]
|
||||
},
|
||||
"request_id": "b59e45e3-5af4-91df-b7c6-9d746fd3297c"
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 OpenAI 协议代理豆包大模型服务
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: doubao
|
||||
apiTokens:
|
||||
- YOUR_DOUBAO_API_KEY
|
||||
modelMapping:
|
||||
'*': YOUR_DOUBAO_ENDPOINT
|
||||
timeout: 1200000
|
||||
```
|
||||
|
||||
### 使用月之暗面配合其原生的文件上下文
|
||||
|
||||
提前上传文件至月之暗面,以文件内容作为上下文使用其 AI 服务。
|
||||
@@ -765,6 +837,7 @@ provider:
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 OpenAI 协议代理混元服务
|
||||
|
||||
**配置信息**
|
||||
@@ -782,9 +855,10 @@ provider:
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
请求脚本:
|
||||
```sh
|
||||
|
||||
请求脚本:
|
||||
|
||||
```shell
|
||||
curl --location 'http://<your higress domain>/v1/chat/completions' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
@@ -948,6 +1022,107 @@ provider:
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 OpenAI 协议代理 GitHub 模型服务
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: github
|
||||
apiTokens:
|
||||
- "YOUR_GITHUB_ACCESS_TOKEN"
|
||||
modelMapping:
|
||||
"gpt-4o": "gpt-4o"
|
||||
"gpt-4": "Phi-3.5-MoE-instruct"
|
||||
"gpt-3.5": "cohere-command-r-08-2024"
|
||||
"text-embedding-3-large": "text-embedding-3-large"
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful assistant."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is the capital of France?"
|
||||
}
|
||||
],
|
||||
"stream": true,
|
||||
"temperature": 1.0,
|
||||
"top_p": 1.0,
|
||||
"max_tokens": 1000,
|
||||
"model": "gpt-4o"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"choices": [
|
||||
{
|
||||
"finish_reason": "stop",
|
||||
"index": 0,
|
||||
"logprobs": null,
|
||||
"message": {
|
||||
"content": "The capital of France is Paris.",
|
||||
"role": "assistant"
|
||||
}
|
||||
}
|
||||
],
|
||||
"created": 1728131051,
|
||||
"id": "chatcmpl-AEy7PU2JImdsD1W6Jw8GigZSEnM2u",
|
||||
"model": "gpt-4o-2024-08-06",
|
||||
"object": "chat.completion",
|
||||
"system_fingerprint": "fp_67802d9a6d",
|
||||
"usage": {
|
||||
"completion_tokens": 7,
|
||||
"prompt_tokens": 24,
|
||||
"total_tokens": 31
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**文本向量请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"input": ["first phrase", "second phrase", "third phrase"],
|
||||
"model": "text-embedding-3-large"
|
||||
}
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"object": "embedding",
|
||||
"index": 0,
|
||||
"embedding": [
|
||||
-0.0012583479,
|
||||
0.0020349282,
|
||||
...
|
||||
0.012051377,
|
||||
-0.0053306012,
|
||||
0.0060688322
|
||||
]
|
||||
}
|
||||
],
|
||||
"model": "text-embedding-3-large",
|
||||
"usage": {
|
||||
"prompt_tokens": 6,
|
||||
"total_tokens": 6
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 OpenAI 协议代理360智脑服务
|
||||
|
||||
**配置信息**
|
||||
@@ -956,7 +1131,7 @@ provider:
|
||||
provider:
|
||||
type: ai360
|
||||
apiTokens:
|
||||
- "YOUR_MINIMAX_API_TOKEN"
|
||||
- "YOUR_360_API_TOKEN"
|
||||
modelMapping:
|
||||
"gpt-4o": "360gpt-turbo-responsibility-8k"
|
||||
"gpt-4": "360gpt2-pro"
|
||||
|
||||
1550
plugins/wasm-go/extensions/ai-proxy/README_EN.md
Normal file
1550
plugins/wasm-go/extensions/ai-proxy/README_EN.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,32 +25,70 @@ import (
|
||||
type PluginConfig struct {
|
||||
// @Title zh-CN AI服务提供商配置
|
||||
// @Description zh-CN AI服务提供商配置,包含API接口、模型和知识库文件等信息
|
||||
providerConfig provider.ProviderConfig `required:"true" yaml:"provider"`
|
||||
providerConfigs []provider.ProviderConfig `required:"true" yaml:"providers"`
|
||||
|
||||
provider provider.Provider `yaml:"-"`
|
||||
activeProviderConfig *provider.ProviderConfig `yaml:"-"`
|
||||
activeProvider provider.Provider `yaml:"-"`
|
||||
}
|
||||
|
||||
func (c *PluginConfig) FromJson(json gjson.Result) {
|
||||
c.providerConfig.FromJson(json.Get("provider"))
|
||||
if providersJson := json.Get("providers"); providersJson.Exists() && providersJson.IsArray() {
|
||||
c.providerConfigs = make([]provider.ProviderConfig, 0)
|
||||
for _, providerJson := range providersJson.Array() {
|
||||
providerConfig := provider.ProviderConfig{}
|
||||
providerConfig.FromJson(providerJson)
|
||||
c.providerConfigs = append(c.providerConfigs, providerConfig)
|
||||
}
|
||||
}
|
||||
|
||||
if providerJson := json.Get("provider"); providerJson.Exists() && providerJson.IsObject() {
|
||||
// TODO: For legacy config support. To be removed later.
|
||||
providerConfig := provider.ProviderConfig{}
|
||||
providerConfig.FromJson(providerJson)
|
||||
c.providerConfigs = []provider.ProviderConfig{providerConfig}
|
||||
c.activeProviderConfig = &providerConfig
|
||||
// Legacy configuration is used and the active provider is determined.
|
||||
// We don't need to continue with the new configuration style.
|
||||
return
|
||||
}
|
||||
|
||||
c.activeProviderConfig = nil
|
||||
|
||||
activeProviderId := json.Get("activeProviderId").String()
|
||||
if activeProviderId != "" {
|
||||
for _, providerConfig := range c.providerConfigs {
|
||||
if providerConfig.GetId() == activeProviderId {
|
||||
c.activeProviderConfig = &providerConfig
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PluginConfig) Validate() error {
|
||||
if err := c.providerConfig.Validate(); err != nil {
|
||||
if c.activeProviderConfig == nil {
|
||||
return nil
|
||||
}
|
||||
if err := c.activeProviderConfig.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PluginConfig) Complete() error {
|
||||
if c.activeProviderConfig == nil {
|
||||
c.activeProvider = nil
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
c.provider, err = provider.CreateProvider(c.providerConfig)
|
||||
c.activeProvider, err = provider.CreateProvider(*c.activeProviderConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *PluginConfig) GetProvider() provider.Provider {
|
||||
return c.provider
|
||||
return c.activeProvider
|
||||
}
|
||||
|
||||
func (c *PluginConfig) GetProviderConfig() provider.ProviderConfig {
|
||||
return c.providerConfig
|
||||
func (c *PluginConfig) GetProviderConfig() *provider.ProviderConfig {
|
||||
return c.activeProviderConfig
|
||||
}
|
||||
|
||||
@@ -56,21 +56,24 @@ static_resources:
|
||||
"@type": "type.googleapis.com/google.protobuf.StringValue"
|
||||
value: |
|
||||
{
|
||||
"provider": {
|
||||
"type": "moonshot",
|
||||
"domain": "api.moonshot.cn",
|
||||
"apiTokens": [
|
||||
"****",
|
||||
"****"
|
||||
],
|
||||
"timeout": 1200000,
|
||||
"modelMapping": {
|
||||
"gpt-3": "moonshot-v1-8k",
|
||||
"gpt-35-turbo": "moonshot-v1-32k",
|
||||
"gpt-4-turbo": "moonshot-v1-128k",
|
||||
"*": "moonshot-v1-8k"
|
||||
},
|
||||
}
|
||||
"activeProviderId": "moonshot",
|
||||
"providers": [
|
||||
{
|
||||
"type": "moonshot",
|
||||
"domain": "api.moonshot.cn",
|
||||
"apiTokens": [
|
||||
"****",
|
||||
"****"
|
||||
],
|
||||
"timeout": 1200000,
|
||||
"modelMapping": {
|
||||
"gpt-3": "moonshot-v1-8k",
|
||||
"gpt-35-turbo": "moonshot-v1-32k",
|
||||
"gpt-4-turbo": "moonshot-v1-128k",
|
||||
"*": "moonshot-v1-8k"
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: envoy.filters.http.router
|
||||
clusters:
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
const (
|
||||
pluginName = "ai-proxy"
|
||||
|
||||
ctxKeyApiName = "apiKey"
|
||||
ctxKeyApiName = "apiName"
|
||||
|
||||
defaultMaxBodyBytes uint32 = 10 * 1024 * 1024
|
||||
)
|
||||
@@ -28,7 +28,7 @@ const (
|
||||
func main() {
|
||||
wrapper.SetCtx(
|
||||
pluginName,
|
||||
wrapper.ParseConfigBy(parseConfig),
|
||||
wrapper.ParseOverrideConfigBy(parseGlobalConfig, parseOverrideRuleConfig),
|
||||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeader),
|
||||
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
|
||||
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
|
||||
@@ -37,8 +37,23 @@ func main() {
|
||||
)
|
||||
}
|
||||
|
||||
func parseConfig(json gjson.Result, pluginConfig *config.PluginConfig, log wrapper.Log) error {
|
||||
// log.Debugf("loading config: %s", json.String())
|
||||
func parseGlobalConfig(json gjson.Result, pluginConfig *config.PluginConfig, log wrapper.Log) error {
|
||||
//log.Debugf("loading global config: %s", json.String())
|
||||
|
||||
pluginConfig.FromJson(json)
|
||||
if err := pluginConfig.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pluginConfig.Complete(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseOverrideRuleConfig(json gjson.Result, global config.PluginConfig, pluginConfig *config.PluginConfig, log wrapper.Log) error {
|
||||
//log.Debugf("loading override rule config: %s", json.String())
|
||||
|
||||
*pluginConfig = global
|
||||
|
||||
pluginConfig.FromJson(json)
|
||||
if err := pluginConfig.Validate(); err != nil {
|
||||
@@ -64,7 +79,8 @@ func onHttpRequestHeader(ctx wrapper.HttpContext, pluginConfig config.PluginConf
|
||||
rawPath := ctx.Path()
|
||||
path, _ := url.Parse(rawPath)
|
||||
apiName := getOpenAiApiName(path.Path)
|
||||
if apiName == "" {
|
||||
providerConfig := pluginConfig.GetProviderConfig()
|
||||
if apiName == "" && !providerConfig.IsOriginal() {
|
||||
log.Debugf("[onHttpRequestHeader] unsupported path: %s", path.Path)
|
||||
_ = util.SendResponse(404, "ai-proxy.unknown_api", util.MimeTypeTextPlain, "API not found: "+path.Path)
|
||||
return types.ActionContinue
|
||||
|
||||
114
plugins/wasm-go/extensions/ai-proxy/provider/cohere.go
Normal file
114
plugins/wasm-go/extensions/ai-proxy/provider/cohere.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
const (
|
||||
cohereDomain = "api.cohere.com"
|
||||
chatCompletionPath = "/v1/chat"
|
||||
)
|
||||
|
||||
type cohereProviderInitializer struct{}
|
||||
|
||||
func (m *cohereProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *cohereProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &cohereProvider{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type cohereProvider struct {
|
||||
config ProviderConfig
|
||||
}
|
||||
|
||||
type cohereTextGenRequest struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
K int `json:"k,omitempty"`
|
||||
P float64 `json:"p,omitempty"`
|
||||
Seed int `json:"seed,omitempty"`
|
||||
StopSequences []string `json:"stop_sequences,omitempty"`
|
||||
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
|
||||
PresencePenalty float64 `json:"presence_penalty,omitempty"`
|
||||
}
|
||||
|
||||
func (m *cohereProvider) GetProviderType() string {
|
||||
return providerTypeCohere
|
||||
}
|
||||
|
||||
func (m *cohereProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestHost(cohereDomain)
|
||||
_ = util.OverwriteRequestPath(chatCompletionPath)
|
||||
_ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *cohereProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
if m.config.protocol == protocolOriginal {
|
||||
request := &cohereTextGenRequest{}
|
||||
if err := json.Unmarshal(body, request); err != nil {
|
||||
return types.ActionContinue, fmt.Errorf("unable to unmarshal request: %v", err)
|
||||
}
|
||||
return m.handleRequestBody(log, request)
|
||||
}
|
||||
origin := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, origin); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
request := m.buildCohereRequest(origin)
|
||||
return m.handleRequestBody(log, request)
|
||||
}
|
||||
|
||||
func (m *cohereProvider) handleRequestBody(log wrapper.Log, request interface{}) (types.Action, error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
err := replaceJsonRequestBody(request, log)
|
||||
if err != nil {
|
||||
_ = util.SendResponse(500, "ai-proxy.cohere.proxy_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
|
||||
func (m *cohereProvider) buildCohereRequest(origin *chatCompletionRequest) *cohereTextGenRequest {
|
||||
if len(origin.Messages) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &cohereTextGenRequest{
|
||||
Message: origin.Messages[0].StringContent(),
|
||||
Model: origin.Model,
|
||||
MaxTokens: origin.MaxTokens,
|
||||
Stream: origin.Stream,
|
||||
Temperature: origin.Temperature,
|
||||
K: origin.N,
|
||||
P: origin.TopP,
|
||||
Seed: origin.Seed,
|
||||
StopSequences: origin.Stop,
|
||||
FrequencyPenalty: origin.FrequencyPenalty,
|
||||
PresencePenalty: origin.PresencePenalty,
|
||||
}
|
||||
}
|
||||
102
plugins/wasm-go/extensions/ai-proxy/provider/doubao.go
Normal file
102
plugins/wasm-go/extensions/ai-proxy/provider/doubao.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
const (
|
||||
doubaoDomain = "ark.cn-beijing.volces.com"
|
||||
doubaoChatCompletionPath = "/api/v3/chat/completions"
|
||||
)
|
||||
|
||||
type doubaoProviderInitializer struct{}
|
||||
|
||||
func (m *doubaoProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *doubaoProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &doubaoProvider{
|
||||
config: config,
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type doubaoProvider struct {
|
||||
config ProviderConfig
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *doubaoProvider) GetProviderType() string {
|
||||
return providerTypeDoubao
|
||||
}
|
||||
|
||||
func (m *doubaoProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
_ = util.OverwriteRequestHost(doubaoDomain)
|
||||
_ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
if m.config.protocol == protocolOriginal {
|
||||
ctx.DontReadRequestBody()
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(doubaoChatCompletionPath)
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *doubaoProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
model := request.Model
|
||||
if model == "" {
|
||||
return types.ActionContinue, errors.New("missing model in chat completion request")
|
||||
}
|
||||
mappedModel := getMappedModel(model, m.config.modelMapping, log)
|
||||
if mappedModel == "" {
|
||||
return types.ActionContinue, errors.New("model becomes empty after applying the configured mapping")
|
||||
}
|
||||
request.Model = mappedModel
|
||||
if m.contextCache != nil {
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, "ai-proxy.doubao.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
insertContextMessage(request, content)
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, "ai-proxy.doubao.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
} else {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
} else {
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, "ai-proxy.doubao.transform_body_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user