mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 21:21:01 +08:00
Compare commits
38 Commits
v1.0.0-rc.
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51d7124454 | ||
|
|
ec6a185adc | ||
|
|
f9ffda288b | ||
|
|
2dbe41324a | ||
|
|
80d6ecfddb | ||
|
|
b23fae7a12 | ||
|
|
2c19d97252 | ||
|
|
efd7ccd5fe | ||
|
|
81fd0d6386 | ||
|
|
176ddc6963 | ||
|
|
44637c2449 | ||
|
|
3e68ae75d1 | ||
|
|
18ad817edb | ||
|
|
d48e0ce773 | ||
|
|
9734ffeb3e | ||
|
|
1421ce8667 | ||
|
|
625c06e58f | ||
|
|
e4a47dfb46 | ||
|
|
6b483189ac | ||
|
|
74ad9a555a | ||
|
|
f6e181ecb6 | ||
|
|
30a5b2ab2b | ||
|
|
91a23cc27e | ||
|
|
51e515d53e | ||
|
|
67274bfa0d | ||
|
|
4f24979579 | ||
|
|
1f4bf8e0b2 | ||
|
|
05608128e2 | ||
|
|
fbdc301f94 | ||
|
|
cf69234eff | ||
|
|
461f7ed675 | ||
|
|
7e358eb1db | ||
|
|
daffd18674 | ||
|
|
48978e5135 | ||
|
|
311d5c21c2 | ||
|
|
e2b4a52c9e | ||
|
|
51cd5e830e | ||
|
|
10d2b41ad5 |
116
.github/workflows/build-and-test.yaml
vendored
116
.github/workflows/build-and-test.yaml
vendored
@@ -23,6 +23,28 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Golang Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
external
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
# test
|
||||
- name: Run Coverage Tests
|
||||
run: GOPROXY="https://proxy.golang.org,direct" make go.test.coverage
|
||||
@@ -38,15 +60,37 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint,coverage-test]
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }}"
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Setup Go"
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: "checkout ${{ github.ref }}"
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Golang Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
path: |-
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
external
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
- name: "Build Higress Binary"
|
||||
run: GOPROXY="https://proxy.golang.org,direct" make build
|
||||
@@ -67,16 +111,78 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: "Setup Go"
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Golang Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
external
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
- name: "Run Ingress Conformance Tests"
|
||||
run: GOPROXY="https://proxy.golang.org,direct" make ingress-conformance-test
|
||||
|
||||
ingress-wasmplugin-test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: "Setup Go"
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: Setup Golang Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
external
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
- name: "Run Ingress WasmPlugins Tests"
|
||||
run: GOPROXY="https://proxy.golang.org,direct" make ingress-wasmplugin-test
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ingress-conformance-test,gateway-conformance-test]
|
||||
needs: [ingress-conformance-test,gateway-conformance-test,ingress-wasmplugin-test]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,4 +12,5 @@ bazel-out
|
||||
bazel-testlogs
|
||||
bazel-wasm-cpp
|
||||
tools/bin/
|
||||
helm/**/charts/**.tgz
|
||||
helm/**/charts/**.tgz
|
||||
tools/hack/cluster.conf
|
||||
@@ -1,7 +1,7 @@
|
||||
/api @johnlanni
|
||||
/envoy @gengleilei @johnlanni @Lynskylate
|
||||
/istio @SpecialYang @johnlanni
|
||||
/pkg @SpecialYang @johnlanni @Charlie17Li
|
||||
/pkg @SpecialYang @johnlanni @Charlie17Li @Xunzhuo
|
||||
/plugins @johnlanni
|
||||
/registry @NameHaibinZhang @johnlanni
|
||||
/test @Xunzhuo
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
## 报告一般问题
|
||||
|
||||
老实说,我们把每一个 Higress 用户都视为非常善良的贡献者。在体验了 Higress 之后,您可能会对项目有一些反馈。然后随时通过 [NEW ISSUE](https://github. com/alibaba/higress/issues/new/choose)打开一个问题。
|
||||
老实说,我们把每一个 Higress 用户都视为非常善良的贡献者。在体验了 Higress 之后,您可能会对项目有一些反馈。然后随时通过 [NEW ISSUE](https://github.com/alibaba/higress/issues/new/choose)打开一个问题。
|
||||
|
||||
因为我们在一个分布式的方式合作项目Higress,我们欣赏写得很好的,详细的,准确的问题报告。为了让沟通更高效,我们希望每个人都可以搜索您的问题是否在搜索列表中。如果您发现它存在,请在现有问题下的评论中添加您的详细信息,而不是打开一个全新的问题。
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@ Security issues are always treated seriously. As our usual principle, we discour
|
||||
## Reporting general issues
|
||||
|
||||
To be honest, we regard every user of Higress as a very kind contributor. After experiencing Higress, you may have
|
||||
some feedback for the project. Then feel free to open an issue via [NEW ISSUE](https://github.
|
||||
com/alibaba/higress/issues/new/choose).
|
||||
some feedback for the project. Then feel free to open an issue via [NEW ISSUE](https://github.com/alibaba/higress/issues/new/choose).
|
||||
|
||||
Since we collaborate project Higress in a distributed way, we appreciate **WELL-WRITTEN**, **DETAILED**, **EXPLICIT** issue reports. To make the communication more efficient, we wish everyone could search if your issue is an existing one in the searching list. If you find it existing, please add your details in comments under the existing issue instead of opening a brand new one.
|
||||
|
||||
|
||||
@@ -128,6 +128,9 @@ build-gateway: prebuild external/package/envoy.tar.gz
|
||||
build-istio: prebuild
|
||||
cd external/istio; rm -rf out; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.pilot" make docker
|
||||
|
||||
build-wasmplugins:
|
||||
./tools/hack/build-wasm-plugins.sh
|
||||
|
||||
pre-install:
|
||||
cp api/kubernetes/customresourcedefinitions.gen.yaml helm/core/crds
|
||||
|
||||
@@ -139,12 +142,15 @@ 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 ?= 1.0.0-rc
|
||||
ISTIO_LATEST_IMAGE_TAG ?= 1.0.0-rc
|
||||
ENVOY_LATEST_IMAGE_TAG ?= 1.0.0
|
||||
ISTIO_LATEST_IMAGE_TAG ?= 1.0.0
|
||||
|
||||
install-dev: pre-install
|
||||
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'
|
||||
|
||||
install-dev-wasmplugin: build-wasmplugins pre-install
|
||||
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true' --set 'global.volumeWasmPlugins=true'
|
||||
|
||||
uninstall:
|
||||
helm uninstall higress -n higress-system
|
||||
|
||||
@@ -197,6 +203,10 @@ gateway-conformance-test:
|
||||
.PHONY: ingress-conformance-test
|
||||
ingress-conformance-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev run-ingress-e2e-test delete-cluster
|
||||
|
||||
# ingress-wasmplugin-test runs ingress wasmplugin tests.
|
||||
.PHONY: ingress-wasmplugin-test
|
||||
ingress-wasmplugin-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev-wasmplugin run-ingress-e2e-test-wasmplugin delete-cluster
|
||||
|
||||
# create-cluster creates a kube cluster with kind.
|
||||
.PHONY: create-cluster
|
||||
create-cluster: $(tools/kind)
|
||||
@@ -221,3 +231,13 @@ run-ingress-e2e-test:
|
||||
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
|
||||
kubectl wait --timeout=10m -n higress-system deployment/higress-gateway --for=condition=Available
|
||||
go test -v -tags conformance ./test/ingress/e2e_test.go --ingress-class=higress --debug=true
|
||||
|
||||
# run-ingress-e2e-test starts to run ingress e2e tests.
|
||||
.PHONY: run-ingress-e2e-test-wasmplugin
|
||||
run-ingress-e2e-test-wasmplugin:
|
||||
@echo -e "\n\033[36mRunning higress conformance tests...\033[0m"
|
||||
@echo -e "\n\033[36mWaiting higress-controller to be ready...\033[0m\n"
|
||||
kubectl wait --timeout=10m -n higress-system deployment/higress-controller --for=condition=Available
|
||||
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
|
||||
kubectl wait --timeout=10m -n higress-system deployment/higress-gateway --for=condition=Available
|
||||
go test -v -tags conformance ./test/ingress/e2e_test.go -isWasmPluginTest=true --ingress-class=higress --debug=true
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
|
||||
[**官网**](https://higress.io/) |
|
||||
[**文档**](https://higress.io/zh-cn/docs/overview/what-is-higress.html) |
|
||||
[**博客**](https://higress.io/zh-cn/blog/index.html) |
|
||||
[**开发指引**](https://higress.io/zh-cn/docs/dev/code.html)
|
||||
[**文档**](https://higress.io/zh-cn/docs/overview/what-is-higress) |
|
||||
[**博客**](https://higress.io/zh-cn/blog) |
|
||||
[**开发指引**](https://higress.io/zh-cn/docs/developers/developers_dev) |
|
||||
[**Higress 企业版**](https://www.aliyun.com/product/aliware/mse?spm=higress-website.topbar.0.0.0)
|
||||
|
||||
|
||||
<p>
|
||||
@@ -20,7 +21,7 @@
|
||||
|
||||
Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源 [Istio](https://github.com/istio/istio) 与 [Envoy](https://github.com/envoyproxy/envoy) 为核心构建的下一代云原生网关。Higress 实现了安全防护网关、流量网关、微服务网关三层网关合一,可以显著降低网关的部署和运维成本。
|
||||
|
||||

|
||||

|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
12
README_EN.md
12
README_EN.md
@@ -4,6 +4,16 @@
|
||||
Next-generation Cloud Native Gateway
|
||||
</h1>
|
||||
|
||||
[](https://github.com/alibaba/higress/actions)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
|
||||
[**Official Site**](https://higress.io/en-us/) |
|
||||
[**Docs**](https://higress.io/en-us/docs/overview/what-is-higress) |
|
||||
[**Blog**](https://higress.io/en-us/blog) |
|
||||
[**Developer**](https://higress.io/en-us/docs/developers/developers_dev) |
|
||||
[**Higress in Cloud**](https://www.alibabacloud.com/product/microservices-engine?spm=higress-website.topbar.0.0.0)
|
||||
|
||||
|
||||
<p>
|
||||
English | <a href="README.md">中文<a/>
|
||||
</p>
|
||||
@@ -13,7 +23,7 @@ Higress is a next-generation cloud-native gateway based on Alibaba's internal ga
|
||||
Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.com/envoyproxy/envoy), Higress realizes the integration of the triple gateway architecture of traffic gateway, microservice gateway and security gateway, thereby greatly reducing the costs of deployment, operation and maintenance.
|
||||
|
||||
<h1 align="center">
|
||||
<img src="https://img.alicdn.com/imgextra/i1/O1CN01vnNawh26mU5C9py9w_!!6000000007704-0-tps-1726-1366.jpg" alt="Higress Architecture">
|
||||
<img src="https://img.alicdn.com/imgextra/i1/O1CN01iO9ph825juHbOIg75_!!6000000007563-2-tps-2483-2024.png" alt="Higress Architecture">
|
||||
</h1>
|
||||
|
||||
|
||||
|
||||
1
helm/core/.helmignore
Normal file
1
helm/core/.helmignore
Normal file
@@ -0,0 +1 @@
|
||||
crds/customresourcedefinitions.gen_lt1.16.yaml
|
||||
@@ -1,7 +1,8 @@
|
||||
apiVersion: v2
|
||||
appVersion: 1.0.0-rc
|
||||
appVersion: 1.0.0
|
||||
description: Helm chart for deploying higress gateways
|
||||
icon: https://higress.io/img/higress_logo_small.png
|
||||
home: http://higress.io/
|
||||
keywords:
|
||||
- higress
|
||||
- gateways
|
||||
@@ -9,4 +10,4 @@ name: higress-core
|
||||
sources:
|
||||
- http://github.com/alibaba/higress
|
||||
type: application
|
||||
version: 1.0.0-rc
|
||||
version: 1.0.0
|
||||
|
||||
407
helm/core/LICENSE
Normal file
407
helm/core/LICENSE
Normal file
@@ -0,0 +1,407 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
|
||||
========================================================================
|
||||
Higress Subcomponents:
|
||||
|
||||
The Higress project contains subcomponents with separate copyright
|
||||
notices and license terms. Your use of the source code for the these
|
||||
subcomponents is subject to the terms and conditions of the following
|
||||
licenses.
|
||||
========================================================================
|
||||
Apache-2.0 licenses
|
||||
========================================================================
|
||||
|
||||
cloud.google.com/go v0.97.0 Apache-2.0
|
||||
cloud.google.com/go/logging v1.4.2 Apache-2.0
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.4.0 Apache-2.0
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible Apache-2.0
|
||||
github.com/Azure/go-autorest/autorest v0.11.20 Apache-2.0
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.15 Apache-2.0
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 Apache-2.0
|
||||
github.com/Azure/go-autorest/logger v0.2.1 Apache-2.0
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 Apache-2.0
|
||||
github.com/Masterminds/goutils v1.1.1 Apache-2.0
|
||||
github.com/aws/aws-sdk-go v1.41.7 Apache-2.0
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 Apache-2.0
|
||||
github.com/cncf/xds/go v0.0.0-20220520190051-1e77728a1eaa Apache-2.0
|
||||
github.com/containerd/continuity v0.1.0 Apache-2.0
|
||||
github.com/docker/cli v20.10.7+incompatible Apache-2.0
|
||||
github.com/docker/distribution v0.0.0-20191216044856-a8371794149d Apache-2.0
|
||||
github.com/docker/go-units v0.4.0 Apache-2.0
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 Apache-2.0
|
||||
github.com/go-logr/logr v0.4.0 Apache-2.0
|
||||
github.com/go-openapi/jsonpointer v0.19.5 Apache-2.0
|
||||
github.com/go-openapi/jsonreference v0.19.5 Apache-2.0
|
||||
github.com/go-openapi/swag v0.19.14 Apache-2.0
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da Apache-2.0
|
||||
github.com/google/btree v1.0.1 Apache-2.0
|
||||
github.com/google/go-containerregistry v0.6.0 Apache-2.0
|
||||
github.com/google/gofuzz v1.2.0 Apache-2.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 Apache-2.0
|
||||
github.com/googleapis/gnostic v0.5.5 Apache-2.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 Apache-2.0
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 Apache-2.0
|
||||
github.com/inconshreveable/mousetrap v1.0.0 Apache-2.0
|
||||
github.com/jmespath/go-jmespath v0.4.0 Apache-2.0
|
||||
github.com/jonboulle/clockwork v0.2.2 Apache-2.0
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 Apache-2.0
|
||||
github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible Apache-2.0
|
||||
github.com/moby/spdystream v0.2.0 Apache-2.0
|
||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 Apache-2.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd Apache-2.0
|
||||
github.com/modern-go/reflect2 v1.0.1 Apache-2.0
|
||||
github.com/opencontainers/go-digest v1.0.0 Apache-2.0
|
||||
github.com/opencontainers/image-spec v1.0.1 Apache-2.0
|
||||
github.com/opencontainers/runc v1.0.2 Apache-2.0
|
||||
github.com/openshift/api v0.0.0-20200713203337-b2494ecb17dd Apache-2.0
|
||||
github.com/prometheus/client_golang v1.11.0 Apache-2.0
|
||||
github.com/prometheus/client_model v0.2.0 Apache-2.0
|
||||
github.com/prometheus/common v0.32.1 Apache-2.0
|
||||
github.com/prometheus/procfs v0.6.0 Apache-2.0
|
||||
github.com/prometheus/statsd_exporter v0.21.0 Apache-2.0
|
||||
github.com/spf13/cobra v1.2.1 Apache-2.0
|
||||
go.opencensus.io v0.23.0 Apache-2.0
|
||||
go.opentelemetry.io/proto/otlp v0.7.0 Apache-2.0
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 Apache-2.0
|
||||
gomodules.xyz/jsonpatch/v3 v3.0.1 Apache-2.0
|
||||
google.golang.org/appengine v1.6.7 Apache-2.0
|
||||
google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a Apache-2.0
|
||||
google.golang.org/grpc v1.42.0 Apache-2.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 Apache-2.0
|
||||
gopkg.in/yaml.v2 v2.4.0 Apache-2.0
|
||||
istio.io/gogo-genproto v0.0.0-20211115195057-0e34bdd2be67 Apache-2.0
|
||||
k8s.io/api v0.22.2 Apache-2.0
|
||||
k8s.io/apiextensions-apiserver v0.22.2 Apache-2.0
|
||||
k8s.io/apimachinery v0.22.2 Apache-2.0
|
||||
k8s.io/cli-runtime v0.22.2 Apache-2.0
|
||||
k8s.io/client-go v0.22.2 Apache-2.0
|
||||
k8s.io/component-base v0.22.2 Apache-2.0
|
||||
k8s.io/klog/v2 v2.10.0 Apache-2.0
|
||||
k8s.io/kube-openapi v0.0.0-20211020163157-7327e2aaee2b Apache-2.0
|
||||
k8s.io/kubectl v0.22.2 Apache-2.0
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b Apache-2.0
|
||||
sigs.k8s.io/controller-runtime v0.10.2 Apache-2.0
|
||||
sigs.k8s.io/gateway-api v0.4.0 Apache-2.0
|
||||
sigs.k8s.io/kustomize/api v0.8.11 Apache-2.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.11.0 Apache-2.0
|
||||
sigs.k8s.io/mcs-api v0.1.0 Apache-2.0
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 Apache-2.0
|
||||
|
||||
========================================================================
|
||||
BSD-2-Clause licenses
|
||||
========================================================================
|
||||
|
||||
github.com/pkg/errors v0.9.1 BSD-2-Clause
|
||||
github.com/russross/blackfriday v1.5.2 BSD-2-Clause
|
||||
|
||||
========================================================================
|
||||
BSD-3-Clause licenses
|
||||
========================================================================
|
||||
|
||||
github.com/PuerkitoBio/purell v1.1.1 BSD-3-Clause
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 BSD-3-Clause
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 BSD-3-Clause
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible BSD-3-Clause
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 BSD-3-Clause
|
||||
github.com/fsnotify/fsnotify v1.5.1 BSD-3-Clause
|
||||
github.com/gogo/protobuf v1.3.2 BSD-3-Clause
|
||||
github.com/golang/protobuf v1.5.2 BSD-3-Clause
|
||||
github.com/google/go-cmp v0.5.6 BSD-3-Clause
|
||||
github.com/google/uuid v1.3.0 BSD-3-Clause
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 BSD-3-Clause
|
||||
github.com/imdario/mergo v0.3.5 BSD-3-Clause
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de BSD-3-Clause
|
||||
github.com/pmezard/go-difflib v1.0.0 BSD-3-Clause
|
||||
github.com/spaolacci/murmur3 v1.1.0 BSD-3-Clause
|
||||
github.com/spf13/pflag v1.0.5 BSD-3-Clause
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 BSD-3-Clause
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 BSD-3-Clause
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309 BSD-3-Clause
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 BSD-3-Clause
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c BSD-3-Clause
|
||||
golang.org/x/sys v0.0.0-20211020174200-9d6173849985 BSD-3-Clause
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d BSD-3-Clause
|
||||
golang.org/x/text v0.3.6 BSD-3-Clause
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac BSD-3-Clause
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 BSD-3-Clause
|
||||
google.golang.org/api v0.59.0 BSD-3-Clause
|
||||
google.golang.org/protobuf v1.27.1 BSD-3-Clause
|
||||
gopkg.in/inf.v0 v0.9.1 BSD-3-Clause
|
||||
|
||||
========================================================================
|
||||
ISC licenses
|
||||
========================================================================
|
||||
|
||||
github.com/davecgh/go-spew v1.1.1 ISC
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 ISC
|
||||
|
||||
========================================================================
|
||||
MIT licenses
|
||||
========================================================================
|
||||
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 MIT
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd MIT
|
||||
github.com/Masterminds/semver/v3 v3.1.1 MIT
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 MIT
|
||||
github.com/Microsoft/go-winio v0.5.0 MIT
|
||||
github.com/Microsoft/hcsshim v0.8.21 MIT
|
||||
github.com/beorn7/perks v1.0.1 MIT
|
||||
github.com/cenkalti/backoff/v4 v4.1.1 MIT
|
||||
github.com/cespare/xxhash/v2 v2.1.1 MIT
|
||||
github.com/docker/docker-credential-helpers v0.6.3 MIT
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d MIT
|
||||
github.com/fvbommel/sortorder v1.0.1 MIT
|
||||
github.com/go-errors/errors v1.0.1 MIT
|
||||
github.com/go-kit/log v0.1.0 MIT
|
||||
github.com/go-logfmt/logfmt v0.5.0 MIT
|
||||
github.com/goccy/go-json v0.4.8 MIT
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0 MIT
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 MIT
|
||||
github.com/huandu/xstrings v1.3.2 MIT
|
||||
github.com/josharian/intern v1.0.0 MIT
|
||||
github.com/json-iterator/go v1.1.11 MIT
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.7 MIT
|
||||
github.com/lestrrat-go/blackmagic v1.0.0 MIT
|
||||
github.com/lestrrat-go/httpcc v1.0.0 MIT
|
||||
github.com/lestrrat-go/iter v1.0.1 MIT
|
||||
github.com/lestrrat-go/jwx v1.2.0 MIT
|
||||
github.com/lestrrat-go/option v1.0.0 MIT
|
||||
github.com/mailru/easyjson v0.7.6 MIT
|
||||
github.com/mitchellh/copystructure v1.2.0 MIT
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 MIT
|
||||
github.com/mitchellh/reflectwalk v1.0.2 MIT
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 MIT
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible MIT
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible MIT
|
||||
github.com/shopspring/decimal v1.2.0 MIT
|
||||
github.com/sirupsen/logrus v1.8.1 MIT
|
||||
github.com/spf13/cast v1.3.1 MIT
|
||||
github.com/stretchr/testify v1.7.0 MIT
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca MIT
|
||||
github.com/yl2chen/cidranger v1.0.2 MIT
|
||||
go.uber.org/atomic v1.9.0 MIT
|
||||
go.uber.org/multierr v1.7.0 MIT
|
||||
go.uber.org/zap v1.19.1 MIT
|
||||
gomodules.xyz/orderedmap v0.1.0 MIT
|
||||
|
||||
========================================================================
|
||||
MIT and Apache-2.0 licenses
|
||||
========================================================================
|
||||
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b MIT and Apache-2.0
|
||||
|
||||
========================================================================
|
||||
MIT and BSD-3-Clause licenses
|
||||
========================================================================
|
||||
|
||||
github.com/ghodss/yaml v1.0.0 MIT and BSD-3-Clause
|
||||
sigs.k8s.io/yaml v1.3.0 MIT and BSD-3-Clause
|
||||
|
||||
========================================================================
|
||||
MPL-2.0 licenses
|
||||
========================================================================
|
||||
|
||||
github.com/hashicorp/errwrap v1.0.0 MPL-2.0
|
||||
github.com/hashicorp/go-multierror v1.1.1 MPL-2.0
|
||||
github.com/hashicorp/go-version v1.3.0 MPL-2.0
|
||||
github.com/hashicorp/golang-lru v0.5.4 MPL-2.0
|
||||
5
helm/core/README.md
Normal file
5
helm/core/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Higress Core Helm Chart
|
||||
|
||||
Installs the core components of cloud-native gateway [Higress](http://higress.io/)
|
||||
|
||||
**Note:** It is highly recommended to install the whole package of Higress. Please visit https://higress.io/docs/user/quickstart/ for details.
|
||||
176
helm/core/crds/customresourcedefinitions.gen_lt1.16.yaml
Normal file
176
helm/core/crds/customresourcedefinitions.gen_lt1.16.yaml
Normal file
@@ -0,0 +1,176 @@
|
||||
# DO NOT EDIT - Generated by Cue OpenAPI generator based on Istio APIs.
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
name: wasmplugins.extensions.higress.io
|
||||
spec:
|
||||
group: extensions.higress.io
|
||||
names:
|
||||
categories:
|
||||
- higress-io
|
||||
- extensions-higress-io
|
||||
kind: WasmPlugin
|
||||
listKind: WasmPluginList
|
||||
plural: wasmplugins
|
||||
singular: wasmplugin
|
||||
scope: Namespaced
|
||||
additionalPrinterColumns:
|
||||
- description: 'CreationTimestamp is a timestamp representing the server time
|
||||
when this object was created. It is not guaranteed to be set in happens-before
|
||||
order across separate operations. Clients may not set this value. It is represented
|
||||
in RFC3339 form and is in UTC. Populated by the system. Read-only. Null for
|
||||
lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
|
||||
JSONPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
version: v1alpha1
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
spec:
|
||||
properties:
|
||||
defaultConfig:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
defaultConfigDisable:
|
||||
type: boolean
|
||||
imagePullPolicy:
|
||||
description: The pull behaviour to be applied when fetching an OCI
|
||||
image.
|
||||
enum:
|
||||
- UNSPECIFIED_POLICY
|
||||
- IfNotPresent
|
||||
- Always
|
||||
type: string
|
||||
imagePullSecret:
|
||||
description: Credentials to use for OCI image pulling.
|
||||
type: string
|
||||
matchRules:
|
||||
items:
|
||||
properties:
|
||||
config:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
configDisable:
|
||||
type: boolean
|
||||
domain:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
ingress:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
phase:
|
||||
description: Determines where in the filter chain this `WasmPlugin`
|
||||
is to be injected.
|
||||
enum:
|
||||
- UNSPECIFIED_PHASE
|
||||
- AUTHN
|
||||
- AUTHZ
|
||||
- STATS
|
||||
type: string
|
||||
pluginConfig:
|
||||
description: The configuration that will be passed on to the plugin.
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
pluginName:
|
||||
type: string
|
||||
priority:
|
||||
description: Determines ordering of `WasmPlugins` in the same `phase`.
|
||||
nullable: true
|
||||
type: integer
|
||||
sha256:
|
||||
description: SHA256 checksum that will be used to verify Wasm module
|
||||
or OCI container.
|
||||
type: string
|
||||
url:
|
||||
description: URL of a Wasm module or OCI container.
|
||||
type: string
|
||||
verificationKey:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
name: mcpbridges.networking.higress.io
|
||||
spec:
|
||||
group: networking.higress.io
|
||||
names:
|
||||
categories:
|
||||
- higress-io
|
||||
kind: McpBridge
|
||||
listKind: McpBridgeList
|
||||
plural: mcpbridges
|
||||
singular: mcpbridge
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
version: v1
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
spec:
|
||||
properties:
|
||||
registries:
|
||||
items:
|
||||
properties:
|
||||
consulNamespace:
|
||||
type: string
|
||||
domain:
|
||||
type: string
|
||||
nacosAccessKey:
|
||||
type: string
|
||||
nacosAddressServer:
|
||||
type: string
|
||||
nacosGroups:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
nacosNamespace:
|
||||
type: string
|
||||
nacosNamespaceId:
|
||||
type: string
|
||||
nacosRefreshInterval:
|
||||
format: int64
|
||||
type: integer
|
||||
nacosSecretKey:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
zkServicesPath:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
@@ -95,3 +95,9 @@ higress: {{ include "controller.name" . }}
|
||||
{{- print "first-party-jwt" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "skywalking.enabled" -}}
|
||||
{{- if and .Values.skywalking.enabled .Values.skywalking.service.address }}
|
||||
true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -122,7 +122,7 @@ data:
|
||||
{{- include "mesh" . }}
|
||||
{{- end }}
|
||||
---
|
||||
{{- if .Values.enableSkywalking }}
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
@@ -154,7 +154,6 @@ data:
|
||||
"type": "LOGICAL_DNS",
|
||||
"connect_timeout": "5s",
|
||||
"http2_protocol_options": {
|
||||
|
||||
},
|
||||
"dns_lookup_family": "V4_ONLY",
|
||||
"lb_policy": "ROUND_ROBIN",
|
||||
@@ -167,8 +166,8 @@ data:
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socket_address": {
|
||||
"address": "{{ .Values.Skywalking.address }}",
|
||||
"port_value": "{{ .Values.Skywalking.port }}"
|
||||
"address": "{{ .Values.skywalking.service.address }}",
|
||||
"port_value": "{{ .Values.skywalking.service.port }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,10 @@ spec:
|
||||
fieldPath: spec.serviceAccountName
|
||||
- name: KUBECONFIG
|
||||
value: /var/run/secrets/remote/config
|
||||
- name: PRIORITIZED_LEADER_ELECTION
|
||||
value: "false"
|
||||
- name: INJECT_ENABLED
|
||||
value: "false"
|
||||
{{- if .Values.pilot.env }}
|
||||
{{- range $key, $val := .Values.pilot.env }}
|
||||
- name: {{ $key }}
|
||||
|
||||
@@ -146,7 +146,7 @@ spec:
|
||||
value: "{{ $.Values.clusterName | default `Kubernetes` }}"
|
||||
- name: INSTANCE_NAME
|
||||
value: "higress-gateway"
|
||||
{{- if .Values.enableSkywalking }}
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- name: ISTIO_BOOTSTRAP_OVERRIDE
|
||||
value: /etc/istio/custom-bootstrap/custom_bootstrap.json
|
||||
{{- end }}
|
||||
@@ -202,10 +202,14 @@ spec:
|
||||
mountPath: /etc/istio/pod
|
||||
- name: proxy-socket
|
||||
mountPath: /etc/istio/proxy
|
||||
{{- if .Values.enableSkywalking }}
|
||||
{{- 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 .Values.gateway.hostNetwork }}
|
||||
hostNetwork: {{ .Values.gateway.hostNetwork }}
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
@@ -242,7 +246,7 @@ spec:
|
||||
- name: config
|
||||
configMap:
|
||||
name: higress-config
|
||||
{{- if .Values.enableSkywalking }}
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- configMap:
|
||||
defaultMode: 420
|
||||
name: higress-custom-bootstrap
|
||||
@@ -274,3 +278,9 @@ spec:
|
||||
containerName: higress-gateway
|
||||
divisor: 1m
|
||||
resource: limits.cpu
|
||||
{{- if .Values.global.volumeWasmPlugins }}
|
||||
- name: local-wasmplugins-volume
|
||||
hostPath:
|
||||
path: /opt/plugins
|
||||
type: Directory
|
||||
{{- end }}
|
||||
|
||||
@@ -45,7 +45,7 @@ global:
|
||||
# Dev builds from prow are on gcr.io
|
||||
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
|
||||
# Default tag for Istio images.
|
||||
tag: 1.0.0-rc
|
||||
tag: 1.0.0
|
||||
|
||||
# Specify image pull policy if default behavior isn't desired.
|
||||
# Default behavior: latest images will be Always else IfNotPresent.
|
||||
@@ -369,7 +369,7 @@ gateway:
|
||||
name: "higress-gateway"
|
||||
replicas: 2
|
||||
image: gateway
|
||||
tag: "1.0.0-rc"
|
||||
tag: "1.0.0"
|
||||
# revision declares which revision this gateway is a part of
|
||||
revision: ""
|
||||
|
||||
@@ -457,7 +457,7 @@ controller:
|
||||
name: "higress-controller"
|
||||
replicas: 1
|
||||
image: higress
|
||||
tag: "1.0.0-rc"
|
||||
tag: "1.0.0"
|
||||
env: {}
|
||||
|
||||
labels: {}
|
||||
@@ -547,7 +547,7 @@ pilot:
|
||||
rollingMaxUnavailable: 25%
|
||||
|
||||
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
|
||||
tag: 1.0.0-rc
|
||||
tag: 1.0.0
|
||||
|
||||
# Can be a full hub/image:tag
|
||||
image: pilot
|
||||
@@ -610,7 +610,8 @@ pilot:
|
||||
|
||||
|
||||
# Skywalking config settings
|
||||
enableSkywalking: false
|
||||
Skywalking:
|
||||
address: "skywalking-oap.higress-system.svc"
|
||||
port: 11800
|
||||
skywalking:
|
||||
enabled: false
|
||||
service:
|
||||
address: ~
|
||||
port: 11800
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
dependencies:
|
||||
- name: higress-core
|
||||
repository: file://../core
|
||||
version: 1.0.0-rc
|
||||
version: 1.0.0
|
||||
- name: higress-console
|
||||
repository: https://higress.io/helm-charts/
|
||||
version: 0.2.0
|
||||
digest: sha256:0a34765ab2125ccf397e81566b4d81a8dc0742a2477d225aad77d9450e4add94
|
||||
generated: "2023-04-08T23:17:37.193119+08:00"
|
||||
version: 1.0.1
|
||||
digest: sha256:cb0808ac6feff2bebf1184969defe5d0b7bf6d12d45e9bd39751df94af731dc0
|
||||
generated: "2023-06-16T10:15:54.5326712+08:00"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
apiVersion: v2
|
||||
appVersion: 1.0.0-rc
|
||||
description: Helm chart for deploying higress gateways
|
||||
appVersion: 1.0.1
|
||||
description: Helm chart for deploying Higress gateways
|
||||
icon: https://higress.io/img/higress_logo_small.png
|
||||
home: http://higress.io/
|
||||
keywords:
|
||||
- higress
|
||||
- gateways
|
||||
@@ -11,9 +12,9 @@ sources:
|
||||
dependencies:
|
||||
- name: higress-core
|
||||
repository: "file://../core"
|
||||
version: 1.0.0-rc
|
||||
version: 1.0.0
|
||||
- name: higress-console
|
||||
repository: "https://higress.io/helm-charts/"
|
||||
version: 0.2.0
|
||||
version: 1.0.1
|
||||
type: application
|
||||
version: 1.0.0-rc
|
||||
version: 1.0.1
|
||||
|
||||
407
helm/higress/LICENSE
Normal file
407
helm/higress/LICENSE
Normal file
@@ -0,0 +1,407 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
|
||||
========================================================================
|
||||
Higress Subcomponents:
|
||||
|
||||
The Higress project contains subcomponents with separate copyright
|
||||
notices and license terms. Your use of the source code for the these
|
||||
subcomponents is subject to the terms and conditions of the following
|
||||
licenses.
|
||||
========================================================================
|
||||
Apache-2.0 licenses
|
||||
========================================================================
|
||||
|
||||
cloud.google.com/go v0.97.0 Apache-2.0
|
||||
cloud.google.com/go/logging v1.4.2 Apache-2.0
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.4.0 Apache-2.0
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible Apache-2.0
|
||||
github.com/Azure/go-autorest/autorest v0.11.20 Apache-2.0
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.15 Apache-2.0
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 Apache-2.0
|
||||
github.com/Azure/go-autorest/logger v0.2.1 Apache-2.0
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 Apache-2.0
|
||||
github.com/Masterminds/goutils v1.1.1 Apache-2.0
|
||||
github.com/aws/aws-sdk-go v1.41.7 Apache-2.0
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 Apache-2.0
|
||||
github.com/cncf/xds/go v0.0.0-20220520190051-1e77728a1eaa Apache-2.0
|
||||
github.com/containerd/continuity v0.1.0 Apache-2.0
|
||||
github.com/docker/cli v20.10.7+incompatible Apache-2.0
|
||||
github.com/docker/distribution v0.0.0-20191216044856-a8371794149d Apache-2.0
|
||||
github.com/docker/go-units v0.4.0 Apache-2.0
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 Apache-2.0
|
||||
github.com/go-logr/logr v0.4.0 Apache-2.0
|
||||
github.com/go-openapi/jsonpointer v0.19.5 Apache-2.0
|
||||
github.com/go-openapi/jsonreference v0.19.5 Apache-2.0
|
||||
github.com/go-openapi/swag v0.19.14 Apache-2.0
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da Apache-2.0
|
||||
github.com/google/btree v1.0.1 Apache-2.0
|
||||
github.com/google/go-containerregistry v0.6.0 Apache-2.0
|
||||
github.com/google/gofuzz v1.2.0 Apache-2.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 Apache-2.0
|
||||
github.com/googleapis/gnostic v0.5.5 Apache-2.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 Apache-2.0
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 Apache-2.0
|
||||
github.com/inconshreveable/mousetrap v1.0.0 Apache-2.0
|
||||
github.com/jmespath/go-jmespath v0.4.0 Apache-2.0
|
||||
github.com/jonboulle/clockwork v0.2.2 Apache-2.0
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 Apache-2.0
|
||||
github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible Apache-2.0
|
||||
github.com/moby/spdystream v0.2.0 Apache-2.0
|
||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 Apache-2.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd Apache-2.0
|
||||
github.com/modern-go/reflect2 v1.0.1 Apache-2.0
|
||||
github.com/opencontainers/go-digest v1.0.0 Apache-2.0
|
||||
github.com/opencontainers/image-spec v1.0.1 Apache-2.0
|
||||
github.com/opencontainers/runc v1.0.2 Apache-2.0
|
||||
github.com/openshift/api v0.0.0-20200713203337-b2494ecb17dd Apache-2.0
|
||||
github.com/prometheus/client_golang v1.11.0 Apache-2.0
|
||||
github.com/prometheus/client_model v0.2.0 Apache-2.0
|
||||
github.com/prometheus/common v0.32.1 Apache-2.0
|
||||
github.com/prometheus/procfs v0.6.0 Apache-2.0
|
||||
github.com/prometheus/statsd_exporter v0.21.0 Apache-2.0
|
||||
github.com/spf13/cobra v1.2.1 Apache-2.0
|
||||
go.opencensus.io v0.23.0 Apache-2.0
|
||||
go.opentelemetry.io/proto/otlp v0.7.0 Apache-2.0
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 Apache-2.0
|
||||
gomodules.xyz/jsonpatch/v3 v3.0.1 Apache-2.0
|
||||
google.golang.org/appengine v1.6.7 Apache-2.0
|
||||
google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a Apache-2.0
|
||||
google.golang.org/grpc v1.42.0 Apache-2.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 Apache-2.0
|
||||
gopkg.in/yaml.v2 v2.4.0 Apache-2.0
|
||||
istio.io/gogo-genproto v0.0.0-20211115195057-0e34bdd2be67 Apache-2.0
|
||||
k8s.io/api v0.22.2 Apache-2.0
|
||||
k8s.io/apiextensions-apiserver v0.22.2 Apache-2.0
|
||||
k8s.io/apimachinery v0.22.2 Apache-2.0
|
||||
k8s.io/cli-runtime v0.22.2 Apache-2.0
|
||||
k8s.io/client-go v0.22.2 Apache-2.0
|
||||
k8s.io/component-base v0.22.2 Apache-2.0
|
||||
k8s.io/klog/v2 v2.10.0 Apache-2.0
|
||||
k8s.io/kube-openapi v0.0.0-20211020163157-7327e2aaee2b Apache-2.0
|
||||
k8s.io/kubectl v0.22.2 Apache-2.0
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b Apache-2.0
|
||||
sigs.k8s.io/controller-runtime v0.10.2 Apache-2.0
|
||||
sigs.k8s.io/gateway-api v0.4.0 Apache-2.0
|
||||
sigs.k8s.io/kustomize/api v0.8.11 Apache-2.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.11.0 Apache-2.0
|
||||
sigs.k8s.io/mcs-api v0.1.0 Apache-2.0
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 Apache-2.0
|
||||
|
||||
========================================================================
|
||||
BSD-2-Clause licenses
|
||||
========================================================================
|
||||
|
||||
github.com/pkg/errors v0.9.1 BSD-2-Clause
|
||||
github.com/russross/blackfriday v1.5.2 BSD-2-Clause
|
||||
|
||||
========================================================================
|
||||
BSD-3-Clause licenses
|
||||
========================================================================
|
||||
|
||||
github.com/PuerkitoBio/purell v1.1.1 BSD-3-Clause
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 BSD-3-Clause
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 BSD-3-Clause
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible BSD-3-Clause
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 BSD-3-Clause
|
||||
github.com/fsnotify/fsnotify v1.5.1 BSD-3-Clause
|
||||
github.com/gogo/protobuf v1.3.2 BSD-3-Clause
|
||||
github.com/golang/protobuf v1.5.2 BSD-3-Clause
|
||||
github.com/google/go-cmp v0.5.6 BSD-3-Clause
|
||||
github.com/google/uuid v1.3.0 BSD-3-Clause
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 BSD-3-Clause
|
||||
github.com/imdario/mergo v0.3.5 BSD-3-Clause
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de BSD-3-Clause
|
||||
github.com/pmezard/go-difflib v1.0.0 BSD-3-Clause
|
||||
github.com/spaolacci/murmur3 v1.1.0 BSD-3-Clause
|
||||
github.com/spf13/pflag v1.0.5 BSD-3-Clause
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 BSD-3-Clause
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 BSD-3-Clause
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309 BSD-3-Clause
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 BSD-3-Clause
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c BSD-3-Clause
|
||||
golang.org/x/sys v0.0.0-20211020174200-9d6173849985 BSD-3-Clause
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d BSD-3-Clause
|
||||
golang.org/x/text v0.3.6 BSD-3-Clause
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac BSD-3-Clause
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 BSD-3-Clause
|
||||
google.golang.org/api v0.59.0 BSD-3-Clause
|
||||
google.golang.org/protobuf v1.27.1 BSD-3-Clause
|
||||
gopkg.in/inf.v0 v0.9.1 BSD-3-Clause
|
||||
|
||||
========================================================================
|
||||
ISC licenses
|
||||
========================================================================
|
||||
|
||||
github.com/davecgh/go-spew v1.1.1 ISC
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 ISC
|
||||
|
||||
========================================================================
|
||||
MIT licenses
|
||||
========================================================================
|
||||
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 MIT
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd MIT
|
||||
github.com/Masterminds/semver/v3 v3.1.1 MIT
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 MIT
|
||||
github.com/Microsoft/go-winio v0.5.0 MIT
|
||||
github.com/Microsoft/hcsshim v0.8.21 MIT
|
||||
github.com/beorn7/perks v1.0.1 MIT
|
||||
github.com/cenkalti/backoff/v4 v4.1.1 MIT
|
||||
github.com/cespare/xxhash/v2 v2.1.1 MIT
|
||||
github.com/docker/docker-credential-helpers v0.6.3 MIT
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d MIT
|
||||
github.com/fvbommel/sortorder v1.0.1 MIT
|
||||
github.com/go-errors/errors v1.0.1 MIT
|
||||
github.com/go-kit/log v0.1.0 MIT
|
||||
github.com/go-logfmt/logfmt v0.5.0 MIT
|
||||
github.com/goccy/go-json v0.4.8 MIT
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0 MIT
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 MIT
|
||||
github.com/huandu/xstrings v1.3.2 MIT
|
||||
github.com/josharian/intern v1.0.0 MIT
|
||||
github.com/json-iterator/go v1.1.11 MIT
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.7 MIT
|
||||
github.com/lestrrat-go/blackmagic v1.0.0 MIT
|
||||
github.com/lestrrat-go/httpcc v1.0.0 MIT
|
||||
github.com/lestrrat-go/iter v1.0.1 MIT
|
||||
github.com/lestrrat-go/jwx v1.2.0 MIT
|
||||
github.com/lestrrat-go/option v1.0.0 MIT
|
||||
github.com/mailru/easyjson v0.7.6 MIT
|
||||
github.com/mitchellh/copystructure v1.2.0 MIT
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 MIT
|
||||
github.com/mitchellh/reflectwalk v1.0.2 MIT
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 MIT
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible MIT
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible MIT
|
||||
github.com/shopspring/decimal v1.2.0 MIT
|
||||
github.com/sirupsen/logrus v1.8.1 MIT
|
||||
github.com/spf13/cast v1.3.1 MIT
|
||||
github.com/stretchr/testify v1.7.0 MIT
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca MIT
|
||||
github.com/yl2chen/cidranger v1.0.2 MIT
|
||||
go.uber.org/atomic v1.9.0 MIT
|
||||
go.uber.org/multierr v1.7.0 MIT
|
||||
go.uber.org/zap v1.19.1 MIT
|
||||
gomodules.xyz/orderedmap v0.1.0 MIT
|
||||
|
||||
========================================================================
|
||||
MIT and Apache-2.0 licenses
|
||||
========================================================================
|
||||
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b MIT and Apache-2.0
|
||||
|
||||
========================================================================
|
||||
MIT and BSD-3-Clause licenses
|
||||
========================================================================
|
||||
|
||||
github.com/ghodss/yaml v1.0.0 MIT and BSD-3-Clause
|
||||
sigs.k8s.io/yaml v1.3.0 MIT and BSD-3-Clause
|
||||
|
||||
========================================================================
|
||||
MPL-2.0 licenses
|
||||
========================================================================
|
||||
|
||||
github.com/hashicorp/errwrap v1.0.0 MPL-2.0
|
||||
github.com/hashicorp/go-multierror v1.1.1 MPL-2.0
|
||||
github.com/hashicorp/go-version v1.3.0 MPL-2.0
|
||||
github.com/hashicorp/golang-lru v0.5.4 MPL-2.0
|
||||
56
helm/higress/README.md
Normal file
56
helm/higress/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Higress Helm Chart
|
||||
|
||||
Installs the cloud-native gateway [Higress](http://higress.io/)
|
||||
|
||||
## Get Repo Info
|
||||
|
||||
```console
|
||||
helm repo add higress.io https://higress.io/helm-charts
|
||||
helm repo update
|
||||
```
|
||||
|
||||
_See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation._
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
To install the chart with the release name `higress`:
|
||||
|
||||
```console
|
||||
helm install higress -n higress-system higress.io/higress --create-namespace --render-subchart-notes
|
||||
```
|
||||
|
||||
## Uninstalling the Chart
|
||||
|
||||
To uninstall/delete the higress deployment:
|
||||
|
||||
```console
|
||||
helm delete higress -n higress-system
|
||||
```
|
||||
|
||||
The command removes all the Kubernetes components associated with the chart and deletes the release.
|
||||
|
||||
## Configuration
|
||||
|
||||
| **Parameter** | **Description** | **Default** |
|
||||
|---|---|---|
|
||||
| **Global Parameters** | | |
|
||||
| global.local | Set to `true` if installing to a local K8s cluster (e.g.: Kind, Rancher Desktop, etc.) | false |
|
||||
| global.ingressClass | [IngressClass](https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/#ingress-class) which is used to filter Ingress resources Higress Controller watches.<br />If there are multiple gateway instances deployed in the cluster, this parameter can be used to distinguish the scope of each gateway instance.<br />There are some special cases for special IngressClass values:<br />1. If set to "nginx", Higress Controller will watch Ingress resources with the `nginx` IngressClass or without any Ingress class.<br />2. If set to empty, Higress Controller will watch all Ingress resources in the K8s cluster. | higress |
|
||||
| global.watchNamespace | If not empty, Higress Controller will only watch resources in the specified namespace. When isolating different business systems using K8s namespace, if each namespace requires a standalone gateway instance, this parameter can be used to confine the Ingress watching of Higress within the given namespace. | "" |
|
||||
| global.disableAlpnH2 | Whether to disable HTTP/2 in ALPN | true |
|
||||
| global.enableStatus | If `true`, Higress Controller will update the `status` field of Ingress resources.<br />When migrating from Nginx Ingress, in order to avoid `status` field of Ingress objects being overwritten, this parameter needs to be set to false, so Higress won't write the entry IP to the `status` field of the corresponding Ingress object. | true |
|
||||
| global.enableIstioAPI | If `true`, Higress Controller will monitor istio resources as well | false |
|
||||
| global.istioNamespace | The namespace istio is installed to | istio-system |
|
||||
| **Core Paramters** | | |
|
||||
| higress-core.gateway.replicas | Number of Higress Gateway pods | 2 |
|
||||
| higress-core.controller.replicas | Number of Higress Controller pods | 1 |
|
||||
| **Console Paramters** | | |
|
||||
| higress-console.replicaCount | Number of Higress Console pods | 1 |
|
||||
| higress-console.service.type | K8s service type used by Higress Console | ClusterIP |
|
||||
| higress-console.domain | Domain used to access Higress Console | console.higress.io |
|
||||
| higress-console.tlsSecretName | Name of Secret resource used by TLS connections. | "" |
|
||||
| higress-console.web.login.prompt | Prompt message to be displayed on the login page | "" |
|
||||
| higress-console.admin.password.value | If not empty, the admin password will be configured to the specified value. | "" |
|
||||
| higress-console.admin.password.length | The length of random admin password generated during installation. Only works when `higress-console.admin.password.value` is not set. | 8 |
|
||||
| higress-console.o11y.enabled | If `true`, o11y suite (Grafana + Promethues) will be installed. | false |
|
||||
| higress-console.pvc.rwxSupported | Set to `false` when installing to a standard K8s cluster and the target cluster doesn't support the ReadWriteMany access mode of PersistentVolumeClaim. | true |
|
||||
@@ -76,7 +76,15 @@ func (i *Ingress) NeedRegexMatch() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return i.Rewrite.RewriteTarget != "" || i.Rewrite.UseRegex
|
||||
return i.Rewrite.RewriteTarget != "" || i.IsPrefixRegexMatch() || i.IsFullPathRegexMatch()
|
||||
}
|
||||
|
||||
func (i *Ingress) IsPrefixRegexMatch() bool {
|
||||
return i.Rewrite.UseRegex
|
||||
}
|
||||
|
||||
func (i *Ingress) IsFullPathRegexMatch() bool {
|
||||
return i.Rewrite.FullPathRegex
|
||||
}
|
||||
|
||||
func (i *Ingress) IsCanary() bool {
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
rewritePath = "rewrite-path"
|
||||
rewriteTarget = "rewrite-target"
|
||||
useRegex = "use-regex"
|
||||
fullPathRegex = "full-path-regex"
|
||||
upstreamVhost = "upstream-vhost"
|
||||
|
||||
re2Regex = "\\$[0-9]"
|
||||
@@ -38,6 +39,7 @@ var (
|
||||
type RewriteConfig struct {
|
||||
RewriteTarget string
|
||||
UseRegex bool
|
||||
FullPathRegex bool
|
||||
RewriteHost string
|
||||
RewritePath string
|
||||
}
|
||||
@@ -52,13 +54,16 @@ func (r rewrite) Parse(annotations Annotations, config *Ingress, _ *GlobalContex
|
||||
rewriteConfig := &RewriteConfig{}
|
||||
rewriteConfig.RewriteTarget, _ = annotations.ParseStringASAP(rewriteTarget)
|
||||
rewriteConfig.UseRegex, _ = annotations.ParseBoolASAP(useRegex)
|
||||
rewriteConfig.FullPathRegex, _ = annotations.ParseBoolForHigress(fullPathRegex)
|
||||
rewriteConfig.RewriteHost, _ = annotations.ParseStringASAP(upstreamVhost)
|
||||
rewriteConfig.RewritePath, _ = annotations.ParseStringForHigress(rewritePath)
|
||||
|
||||
if rewriteConfig.RewritePath == "" && rewriteConfig.RewriteTarget != "" {
|
||||
// When rewrite target is present and not empty,
|
||||
// we will enforce regex match on all rules in this ingress.
|
||||
rewriteConfig.UseRegex = true
|
||||
if !rewriteConfig.UseRegex && !rewriteConfig.FullPathRegex {
|
||||
rewriteConfig.UseRegex = true
|
||||
}
|
||||
|
||||
// We should convert nginx regex rule to envoy regex rule.
|
||||
rewriteConfig.RewriteTarget = convertToRE2(rewriteConfig.RewriteTarget)
|
||||
@@ -108,12 +113,13 @@ func convertToRE2(target string) string {
|
||||
|
||||
func NeedRegexMatch(annotations map[string]string) bool {
|
||||
target, _ := Annotations(annotations).ParseStringASAP(rewriteTarget)
|
||||
regex, _ := Annotations(annotations).ParseBoolASAP(useRegex)
|
||||
useRegex, _ := Annotations(annotations).ParseBoolASAP(useRegex)
|
||||
fullPathRegex, _ := Annotations(annotations).ParseBoolForHigress(fullPathRegex)
|
||||
|
||||
return regex || target != ""
|
||||
return useRegex || target != "" || fullPathRegex
|
||||
}
|
||||
|
||||
func needRewriteConfig(annotations Annotations) bool {
|
||||
return annotations.HasASAP(rewriteTarget) || annotations.HasASAP(useRegex) ||
|
||||
annotations.HasASAP(upstreamVhost) || annotations.HasHigress(rewritePath)
|
||||
annotations.HasASAP(upstreamVhost) || annotations.HasHigress(rewritePath) || annotations.HasHigress(fullPathRegex)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,11 @@ const (
|
||||
|
||||
Prefix PathType = "prefix"
|
||||
|
||||
Regex PathType = "regex"
|
||||
// PrefixRegex :if PathType is PrefixRegex, then the /foo/bar/[A-Z0-9]{3} is actually ^/foo/bar/[A-Z0-9]{3}.*
|
||||
PrefixRegex PathType = "prefixRegex"
|
||||
|
||||
// FullPathRegex :if PathType is FullPathRegex, then the /foo/bar/[A-Z0-9]{3} is actually ^/foo/bar/[A-Z0-9]{3}$
|
||||
FullPathRegex PathType = "fullPathRegex"
|
||||
|
||||
DefaultStatusUpdateInterval = 10 * time.Second
|
||||
|
||||
|
||||
@@ -43,11 +43,11 @@ func TestConstructRouteName(t *testing.T) {
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
Host: "*.test.com",
|
||||
OriginPathType: Regex,
|
||||
OriginPathType: PrefixRegex,
|
||||
OriginPath: "/test/(.*)/?[0-9]",
|
||||
HTTPRoute: &networking.HTTPRoute{},
|
||||
},
|
||||
expect: "*.test.com-regex-/test/(.*)/?[0-9]",
|
||||
expect: "*.test.com-prefixRegex-/test/(.*)/?[0-9]",
|
||||
},
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
@@ -390,7 +390,7 @@ func TestSortRoutes(t *testing.T) {
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
},
|
||||
Host: "test.com",
|
||||
OriginPathType: Regex,
|
||||
OriginPathType: PrefixRegex,
|
||||
OriginPath: "/d(.*)",
|
||||
ClusterId: "cluster1",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
|
||||
@@ -534,8 +534,12 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
||||
|
||||
var pathType common.PathType
|
||||
originPath := httpPath.Path
|
||||
if wrapper.AnnotationsConfig.NeedRegexMatch() {
|
||||
pathType = common.Regex
|
||||
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
|
||||
if annotationsConfig.IsPrefixRegexMatch() {
|
||||
pathType = common.PrefixRegex
|
||||
} else if annotationsConfig.IsFullPathRegexMatch() {
|
||||
pathType = common.FullPathRegex
|
||||
}
|
||||
} else {
|
||||
switch *httpPath.PathType {
|
||||
case ingress.PathTypeExact:
|
||||
@@ -741,8 +745,12 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
|
||||
|
||||
var pathType common.PathType
|
||||
originPath := httpPath.Path
|
||||
if wrapper.AnnotationsConfig.NeedRegexMatch() {
|
||||
pathType = common.Regex
|
||||
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
|
||||
if annotationsConfig.IsPrefixRegexMatch() {
|
||||
pathType = common.PrefixRegex
|
||||
} else if annotationsConfig.IsFullPathRegexMatch() {
|
||||
pathType = common.FullPathRegex
|
||||
}
|
||||
} else {
|
||||
switch *httpPath.PathType {
|
||||
case ingress.PathTypeExact:
|
||||
@@ -1166,10 +1174,14 @@ func (c *controller) generateHttpMatches(pathType common.PathType, path string,
|
||||
|
||||
httpMatch := &networking.HTTPMatchRequest{}
|
||||
switch pathType {
|
||||
case common.Regex:
|
||||
case common.PrefixRegex:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Regex{Regex: path + ".*"},
|
||||
}
|
||||
case common.FullPathRegex:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Regex{Regex: path + "$"},
|
||||
}
|
||||
case common.Exact:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Exact{Exact: path},
|
||||
|
||||
@@ -39,8 +39,7 @@ type statusSyncer struct {
|
||||
|
||||
watchedNamespace string
|
||||
|
||||
ingressLister ingresslister.IngressLister
|
||||
ingressClassLister ingresslister.IngressClassLister
|
||||
ingressLister ingresslister.IngressLister
|
||||
// search service in the mse vpc
|
||||
serviceLister listerv1.ServiceLister
|
||||
}
|
||||
@@ -48,11 +47,10 @@ type statusSyncer struct {
|
||||
// newStatusSyncer creates a new instance
|
||||
func newStatusSyncer(localKubeClient, client kubelib.Client, controller *controller, namespace string) *statusSyncer {
|
||||
return &statusSyncer{
|
||||
client: client,
|
||||
controller: controller,
|
||||
watchedNamespace: namespace,
|
||||
ingressLister: client.KubeInformer().Networking().V1beta1().Ingresses().Lister(),
|
||||
ingressClassLister: client.KubeInformer().Networking().V1beta1().IngressClasses().Lister(),
|
||||
client: client,
|
||||
controller: controller,
|
||||
watchedNamespace: namespace,
|
||||
ingressLister: client.KubeInformer().Networking().V1beta1().Ingresses().Lister(),
|
||||
// search service in the mse vpc
|
||||
serviceLister: localKubeClient.KubeInformer().Core().V1().Services().Lister(),
|
||||
}
|
||||
|
||||
@@ -517,8 +517,12 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
||||
|
||||
var pathType common.PathType
|
||||
originPath := httpPath.Path
|
||||
if wrapper.AnnotationsConfig.NeedRegexMatch() {
|
||||
pathType = common.Regex
|
||||
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
|
||||
if annotationsConfig.IsPrefixRegexMatch() {
|
||||
pathType = common.PrefixRegex
|
||||
} else if annotationsConfig.IsFullPathRegexMatch() {
|
||||
pathType = common.FullPathRegex
|
||||
}
|
||||
} else {
|
||||
switch *httpPath.PathType {
|
||||
case ingress.PathTypeExact:
|
||||
@@ -610,10 +614,14 @@ func (c *controller) generateHttpMatches(pathType common.PathType, path string,
|
||||
|
||||
httpMatch := &networking.HTTPMatchRequest{}
|
||||
switch pathType {
|
||||
case common.Regex:
|
||||
case common.PrefixRegex:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Regex{Regex: path + ".*"},
|
||||
}
|
||||
case common.FullPathRegex:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Regex{Regex: path + "$"},
|
||||
}
|
||||
case common.Exact:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Exact{Exact: path},
|
||||
@@ -747,8 +755,12 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
|
||||
|
||||
var pathType common.PathType
|
||||
originPath := httpPath.Path
|
||||
if wrapper.AnnotationsConfig.NeedRegexMatch() {
|
||||
pathType = common.Regex
|
||||
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
|
||||
if annotationsConfig.IsPrefixRegexMatch() {
|
||||
pathType = common.PrefixRegex
|
||||
} else if annotationsConfig.IsFullPathRegexMatch() {
|
||||
pathType = common.FullPathRegex
|
||||
}
|
||||
} else {
|
||||
switch *httpPath.PathType {
|
||||
case ingress.PathTypeExact:
|
||||
|
||||
@@ -40,8 +40,7 @@ type statusSyncer struct {
|
||||
|
||||
watchedNamespace string
|
||||
|
||||
ingressLister ingresslister.IngressLister
|
||||
ingressClassLister ingresslister.IngressClassLister
|
||||
ingressLister ingresslister.IngressLister
|
||||
// search service in the mse vpc
|
||||
serviceLister listerv1.ServiceLister
|
||||
}
|
||||
@@ -49,11 +48,10 @@ type statusSyncer struct {
|
||||
// newStatusSyncer creates a new instance
|
||||
func newStatusSyncer(localKubeClient, client kubelib.Client, controller *controller, namespace string) *statusSyncer {
|
||||
return &statusSyncer{
|
||||
client: client,
|
||||
controller: controller,
|
||||
watchedNamespace: namespace,
|
||||
ingressLister: client.KubeInformer().Networking().V1().Ingresses().Lister(),
|
||||
ingressClassLister: client.KubeInformer().Networking().V1().IngressClasses().Lister(),
|
||||
client: client,
|
||||
controller: controller,
|
||||
watchedNamespace: namespace,
|
||||
ingressLister: client.KubeInformer().Networking().V1().Ingresses().Lister(),
|
||||
// search service in the mse vpc
|
||||
serviceLister: localKubeClient.KubeInformer().Core().V1().Services().Lister(),
|
||||
}
|
||||
|
||||
286
plugins/wasm-cpp/extensions/hmac_auth/README_EN.md
Normal file
286
plugins/wasm-cpp/extensions/hmac_auth/README_EN.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# 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.
|
||||
|
||||
# Configuration Fields
|
||||
|
||||
| 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. |
|
||||
|
||||
The configuration fields for each item in `consumers` are as follows :
|
||||
|
||||
| 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 `_rules_` are as follows:
|
||||
|
||||
| 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
|
||||
```yaml
|
||||
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.
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- key: appKey-example-1
|
||||
secret: appSecret-example-1
|
||||
name: consumer-1
|
||||
- key: appKey-example-2
|
||||
secret: appSecret-example-2
|
||||
name: consumer-2
|
||||
```
|
||||
|
||||
|
||||
# Description of Signing Mechanism
|
||||
|
||||
## 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 :
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||
```text
|
||||
HTTPMethod
|
||||
Accept
|
||||
Content-MD5
|
||||
Content-Type
|
||||
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 :
|
||||
|
||||
|
||||
```java
|
||||
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:
|
||||
```text
|
||||
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`.
|
||||
- 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:
|
||||
|
||||
```text
|
||||
Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueN
|
||||
```
|
||||
|
||||
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 :
|
||||
```text
|
||||
POST /http2test/test?param1=test HTTP/1.1
|
||||
host:api.aliyun.com
|
||||
accept:application/json; charset=utf-8
|
||||
ca_version:1
|
||||
content-type:application/x-www-form-urlencoded; charset=utf-8
|
||||
x-ca-timestamp:1525872629832
|
||||
date:Wed, 09 May 2018 13:30:29 GMT+00:00
|
||||
user-agent:ALIYUN-ANDROID-DEMO
|
||||
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
|
||||
content-length:33
|
||||
username=xiaoming&password=123456789
|
||||
```
|
||||
|
||||
The correct generated signature string is :
|
||||
```text
|
||||
POST
|
||||
application/json; charset=utf-8
|
||||
application/x-www-form-urlencoded; charset=utf-8
|
||||
Wed, 09 May 2018 13:30:29 GMT+00:00
|
||||
x-ca-key:203753385
|
||||
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
|
||||
x-ca-signature-method:HmacSHA256
|
||||
x-ca-timestamp:1525872629832
|
||||
/http2test/test?param1=test&password=123456789&username=xiaoming
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
```java
|
||||
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
|
||||
byte[] secretBytes = secret.getBytes("UTF-8");
|
||||
hmacSha256.init(new SecretKeySpec(secretBytes, 0, secretBytes.length, "HmacSHA256"));
|
||||
byte[] result = hmacSha256.doFinal(stringToSign.getBytes("UTF-8"));
|
||||
String sign = Base64.encodeBase64String(result);
|
||||
```
|
||||
|
||||
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 :
|
||||
|
||||
```text
|
||||
POST /http2test/test?param1=test HTTP/1.1
|
||||
host:api.aliyun.com
|
||||
accept:application/json; charset=utf-8
|
||||
ca_version:1
|
||||
content-type:application/x-www-form-urlencoded; charset=utf-8
|
||||
x-ca-timestamp:1525872629832
|
||||
date:Wed, 09 May 2018 13:30:29 GMT+00:00
|
||||
user-agent:ALIYUN-ANDROID-DEMO
|
||||
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
|
||||
x-ca-key:203753385
|
||||
x-ca-signature-method:HmacSHA256
|
||||
x-ca-signature-headers:x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method
|
||||
x-ca-signature:xfX+bZxY2yl7EB/qdoDy9v/uscw3Nnj1pgoU+Bm6xdM=
|
||||
content-length:33
|
||||
username=xiaoming&password=123456789
|
||||
```
|
||||
|
||||
## Server-side Signature Verification Method
|
||||
|
||||
### 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 :
|
||||

|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
```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. |
|
||||
|
||||
|
||||
@@ -244,12 +244,14 @@ public class GenerateJwtDemo {
|
||||
- 只有当`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中的指定字段,要求必须是字符串或无符号整数类型 |
|
||||
|
||||
@@ -390,10 +390,10 @@ consumers:
|
||||
```
|
||||
|
||||
# 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. |
|
||||
|
||||
| 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. |
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
| limit_keys | array of object | 必填 | - | 配置匹配键值后的限流次数 |
|
||||
|
||||
`limit_keys`中每一项的配置字段说明
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| key | string | 必填 | - | 匹配的键值 |
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
| limit_keys | array of object | Required | - | Rate-limiting thresholds when matching specific key-values |
|
||||
|
||||
Field descriptions of `limit_keys` items:
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| key | string | Required | - | Value to match of the specific key |
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
ARG BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.19-tinygo0.27.0
|
||||
ARG BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.19-tinygo0.25.0-oras1.0.0
|
||||
FROM $BUILDER as builder
|
||||
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY}
|
||||
|
||||
ARG PLUGIN_NAME=hello-world
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
@@ -1,84 +1,115 @@
|
||||
# The Dockerfile for wasm-go builder only support amd64 and arm64 yet.
|
||||
# If you want to build on another architecture, the following information may be helpful.
|
||||
#
|
||||
# - arch: amd64 \
|
||||
# - arch: amd64
|
||||
# base image: docker.io/ubuntu
|
||||
# go_url: https://golang.google.cn/dl/go1.20.1.linux-amd64.tar.gz"
|
||||
# tinygo_url: https://github.com/tinygo-org/tinygo/releases/download/v0.27.0/tinygo_0.27.0_amd64.deb
|
||||
# tinygo_url: https://github.com/alibaba/higress/releases/download/v1.0.0-rc/higress-tinygo0.25.0.linux-amd64.tar.gz
|
||||
# oras_url: https://github.com/oras-project/oras/releases/download/v1.0.0/oras_1.0.0_linux_amd64.tar.gz
|
||||
#
|
||||
# - arch: arm64
|
||||
# base image: docker.io/ubuntu
|
||||
# go_url: https://golang.google.cn/dl/go1.20.1.linux-arm64.tar.gz
|
||||
# tinygo_url: https://github.com/tinygo-org/tinygo/releases/download/v0.27.0/tinygo_0.27.0_arm64.deb
|
||||
# tinygo_url: https://github.com/alibaba/higress/releases/download/v1.0.0-rc/higress-tinygo0.25.0.linux-arm64.tar.gz
|
||||
# oras_url: https://github.com/oras-project/oras/releases/download/v1.0.0/oras_1.0.0_linux_arm64.tar.gz
|
||||
#
|
||||
# - arch: armel
|
||||
# base image: build yourself
|
||||
# go_url: install from source code
|
||||
# tinygo_url: build yourself
|
||||
# oras_url: build your self
|
||||
#
|
||||
# - arch: i386
|
||||
# base image: build yourself
|
||||
# go_url: https://dl.google.com/go/go1.20.1.linux-386.tar.gz
|
||||
# tinygo_url: build yourself
|
||||
# oras_url: build your self
|
||||
#
|
||||
# - arch: mips64el
|
||||
# base image: build your self
|
||||
# go_url: https://dl.google.com/go/go1.20.1.linux-386.tar.gz
|
||||
# tinygo_url: build your self
|
||||
# oras_url: build your self
|
||||
#
|
||||
# - arch: ppc64el
|
||||
# base image: build your self
|
||||
# go_url: https://dl.google.com/go/go1.20.1.linux-ppc64le.tar.gz
|
||||
# tinygo_url: build your self
|
||||
# oras_url: build your self
|
||||
#
|
||||
# - arch: s390x
|
||||
# base image: docker.io/ubuntu
|
||||
# go_url: https://dl.google.com/go/go1.20.1.linux-s390x.tar.gz
|
||||
# tinygo_url: build your self
|
||||
# oras_url: build your self
|
||||
#
|
||||
# - arch: armhf
|
||||
# base image: build your self
|
||||
# go_url: https://golang.google.cn/dl/go1.20.1.linux-armv6l.tar.gz
|
||||
# tinygo_url: https://github.com/tinygo-org/tinygo/releases/download/v0.27.0/tinygo_0.27.0_armhf.deb
|
||||
# tinygo_url: https://github.com/tinygo-org/tinygo/releases/download/v0.25.0/tinygo_0.25.0_armhf.deb
|
||||
# oras_url: build your self
|
||||
|
||||
ARG BASE_IMAGE=docker.io/ubuntu
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
ARG GO_VERSION
|
||||
ARG TINYGO_VERSION
|
||||
ARG ORAS_VERSION
|
||||
ARG HIGRESS_VERSION
|
||||
ARG USE_HIGRESS_TINYGO
|
||||
|
||||
LABEL go_version=$GO_VERSION tinygo_version=$TINYGO_VERSION
|
||||
LABEL go_version=$GO_VERSION tinygo_version=$TINYGO_VERSION oras_version=$ORAS_VERSION
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y wget build-essential \
|
||||
&& apt-get install -y wget \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
RUN arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
|
||||
go_url=; \
|
||||
go_url=; \
|
||||
tinygo_url=; \
|
||||
go_version=${GO_VERSION:-1.19}; \
|
||||
tinygo_version=${TINYGO_VERSION:-0.27.0}; \
|
||||
tinygo_version=${TINYGO_VERSION:-0.25.0}; \
|
||||
oras_version=${ORAS_VERSION:-1.0.0}; \
|
||||
higress_version=${HIGRESS_VERSION:-1.0.0-rc}; \
|
||||
use_higress_tinygo=${USE_HIGRESS_TINYGO:-false}; \
|
||||
echo "arch: '$arch'"; \
|
||||
echo "go go_version: '$go_version'"; \
|
||||
echo "tinygo_version: '$tinygo_version'"; \
|
||||
case "$arch" in \
|
||||
'amd64') \
|
||||
echo "oras_version: '$oras_version'"; \
|
||||
echo "higress_version: '$higress_version'"; \
|
||||
echo "use_higress_tinygo: '$use_higress_tinygo'"; \
|
||||
case "$arch" in \
|
||||
'amd64') \
|
||||
go_url="https://golang.google.cn/dl/go$go_version.linux-amd64.tar.gz"; \
|
||||
tinygo_url="https://github.com/tinygo-org/tinygo/releases/download/v$tinygo_version/tinygo_${tinygo_version}_amd64.deb"; \
|
||||
;; \
|
||||
'arm64') \
|
||||
if [ "$use_higress_tinygo" = "true" ]; \
|
||||
then \
|
||||
tinygo_url="https://github.com/alibaba/higress/releases/download/v$higress_version/higress-tinygo${tinygo_version}.linux-amd64.tar.gz"; \
|
||||
else \
|
||||
tinygo_url="https://github.com/tinygo-org/tinygo/releases/download/v$tinygo_version/tinygo${tinygo_version}.linux-amd64.tar.gz"; \
|
||||
fi; \
|
||||
oras_url="https://github.com/oras-project/oras/releases/download/v$oras_version/oras_${oras_version}_linux_amd64.tar.gz"; \
|
||||
;; \
|
||||
'arm64') \
|
||||
go_url="https://golang.google.cn/dl/go$go_version.linux-arm64.tar.gz"; \
|
||||
tinygo_url="https://github.com/tinygo-org/tinygo/releases/download/v$tinygo_version/tinygo_${tinygo_version}_arm64.deb"; \
|
||||
;; \
|
||||
*) echo >&2 "error: unsupported architecture '$arch' "; exit 1 ;; \
|
||||
esac; \
|
||||
if [ "$use_higress_tinygo" = "true" ]; \
|
||||
then \
|
||||
tinygo_url="https://github.com/alibaba/higress/releases/download/v$higress_version/higress-tinygo${tinygo_version}.linux-arm64.tar.gz"; \
|
||||
else \
|
||||
tinygo_url="https://github.com/tinygo-org/tinygo/releases/download/v$tinygo_version/tinygo${tinygo_version}.linux-arm64.tar.gz"; \
|
||||
fi; \
|
||||
oras_url="https://github.com/oras-project/oras/releases/download/v$oras_version/oras_${oras_version}_linux_arm64.tar.gz"; \
|
||||
;; \
|
||||
*) echo >&2 "error: unsupported architecture '$arch' "; exit 1 ;; \
|
||||
esac; \
|
||||
echo "go_url: '$go_url'"; \
|
||||
echo "tinygo_url: '$tinygo_url'"; \
|
||||
wget -O go.tgz "$go_url" --progress=dot:giga; \
|
||||
wget -O tinygo.deb "$tinygo_url" --progress=dot:giga; \
|
||||
echo "Download complete"; \
|
||||
rm -rf /usr/local/go && tar -C /usr/local -xzf go.tgz && rm -rf go.tgz; \
|
||||
dpkg -i tinygo.deb && rm -rf tinygo.deb
|
||||
echo "tinygo_url: '$tinygo_url'"; \
|
||||
wget -O tinygo.tgz "$tinygo_url" --progress=dot:giga; \
|
||||
rm -rf /usr/local/tinygo && tar -C /usr/local -xzf tinygo.tgz && rm -rf tinygo.tgz; \
|
||||
echo "oras_url: '$oras_url'"; \
|
||||
wget -O oras.tgz "$oras_url" --progress=dot:giga; \
|
||||
tar -C /usr/local/bin -xzf oras.tgz && rm -rf oras.tgz; \
|
||||
echo "done";
|
||||
|
||||
ENV PATH=$PATH:/usr/local/go/bin:/usr/local/bin
|
||||
ENV PATH=$PATH:/usr/local/go/bin:/usr/local/tinygo/bin:/usr/local/bin
|
||||
|
||||
@@ -1,41 +1,58 @@
|
||||
PLUGIN_NAME ?= hello-world
|
||||
REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
|
||||
GO_VERSION ?= 1.19
|
||||
TINYGO_VERSION ?= 0.27.0
|
||||
BUILDER ?= ${REGISTRY}wasm-go-builder:go${GO_VERSION}-tinygo${TINYGO_VERSION}
|
||||
TINYGO_VERSION ?= 0.25.0
|
||||
ORAS_VERSION ?= 1.0.0
|
||||
HIGRESS_VERSION ?= 1.0.0-rc
|
||||
USE_HIGRESS_TINYGO ?= true
|
||||
BUILDER ?= ${REGISTRY}wasm-go-builder:go${GO_VERSION}-tinygo${TINYGO_VERSION}-oras${ORAS_VERSION}
|
||||
BUILD_TIME := $(shell date "+%Y%m%d-%H%M%S")
|
||||
COMMIT_ID := $(shell git rev-parse --short HEAD 2>/dev/null)
|
||||
IMG ?= ${REGISTRY}${PLUGIN_NAME}:${BUILD_TIME}-${COMMIT_ID}
|
||||
IMAGE_TAG = $(if $(strip $(PLUGIN_VERSION)),${PLUGIN_VERSION},${BUILD_TIME}-${COMMIT_ID})
|
||||
IMG ?= ${REGISTRY}${PLUGIN_NAME}:${IMAGE_TAG}
|
||||
GOPROXY := $(shell go env GOPROXY)
|
||||
|
||||
.DEFAULT:
|
||||
build:
|
||||
DOCKER_BUILDKIT=1 docker build --build-arg PLUGIN_NAME=${PLUGIN_NAME} \
|
||||
--build-arg BUILDER=${BUILDER} \
|
||||
--build-arg GOPROXY=$(GOPROXY) \
|
||||
-t ${IMG} \
|
||||
--output extensions/${PLUGIN_NAME} \
|
||||
.
|
||||
@echo ""
|
||||
@echo "image: ${IMG}"
|
||||
@echo "output wasm file: extensions/${PLUGIN_NAME}/plugin.wasm"
|
||||
|
||||
build-push: build
|
||||
build-image:
|
||||
DOCKER_BUILDKIT=1 docker build --build-arg PLUGIN_NAME=${PLUGIN_NAME} \
|
||||
--build-arg BUILDER=${BUILDER} \
|
||||
--build-arg GOPROXY=$(GOPROXY) \
|
||||
-t ${IMG} \
|
||||
--load \
|
||||
.
|
||||
@echo ""
|
||||
@echo "image: ${IMG}"
|
||||
|
||||
build-push: build-image
|
||||
docker push ${IMG}
|
||||
|
||||
# builder:
|
||||
# To build a wasm-go-builder image.
|
||||
# e.g.
|
||||
# REGISTRY=<your_docker_registry> make builder
|
||||
# If you want to use Go/TinyGo with another version, please modify GO_VERSION/TINYGO_VERSION.
|
||||
# If you want to use Go/TinyGo/Oras with another version, please modify GO_VERSION/TINYGO_VERSION/ORAS_VERSION.
|
||||
# After your wasm-go-builder image is built, you can use it to build plugin image.
|
||||
# e.g.
|
||||
# PLUGIN_NAME=request-block BUILDER=<your-wasm-go-builder> make
|
||||
builder:
|
||||
BUILDER=$(REGISTRY)wasm-go-builder:go$(GO_VERSION)-tinygo$(TINYGO_VERSION)
|
||||
docker buildx build --no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--build-arg BASE_IMAGE=docker.io/ubuntu \
|
||||
--build-arg GO_VERSION=$(GO_VERSION) \
|
||||
--build-arg TINYGO_VERSION=$(TINYGO_VERSION) \
|
||||
--build-arg ORAS_VERSION=$(ORAS_VERSION) \
|
||||
--build-arg HIGRESS_VERSION=$(HIGRESS_VERSION) \
|
||||
--build-arg USE_HIGRESS_TINYGO=$(USE_HIGRESS_TINYGO) \
|
||||
-f DockerfileBuilder \
|
||||
-t ${BUILDER} \
|
||||
--push \
|
||||
|
||||
230
plugins/wasm-go/extensions/cors/README.md
Normal file
230
plugins/wasm-go/extensions/cors/README.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# 功能说明
|
||||
|
||||
`cors` 插件可以为服务端启用 CORS(Cross-Origin Resource Sharing,跨域资源共享)的返回 http 响应头。
|
||||
|
||||
# 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|-----------------------|-----------------|-------|---------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| allow_origins | array of string | 选填 | * | 允许跨域访问的 Origin,格式为 scheme://host:port,示例如 http://example.com:8081。当 allow_credentials 为 false 时,可以使用 * 来表示允许所有 Origin 通过 |
|
||||
| allow_origin_patterns | array of string | 选填 | - | 允许跨域访问的 Origin 模式匹配, 用 * 匹配域名或者端口, <br/>比如 http://*.example.com -- 匹配域名, http://*.example.com:[8080,9090] -- 匹配域名和指定端口, http://*.example.com:[*] -- 匹配域名和所有端口。单独 * 表示匹配所有域名和端口 |
|
||||
| allow_methods | array of string | 选填 | GET, PUT, POST, DELETE, PATCH, OPTIONS | 允许跨域访问的 Method,比如:GET,POST 等。可以使用 * 来表示允许所有 Method。 |
|
||||
| allow_headers | array of string | 选填 | DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,<br/>If-Modified-Since,Cache-Control,Content-Type,Authorization | 允许跨域访问时请求方携带哪些非 CORS 规范以外的 Header。可以使用 * 来表示允许任意 Header。 |
|
||||
| expose_headers | array of string | 选填 | - | 允许跨域访问时响应方携带哪些非 CORS 规范以外的 Header。可以使用 * 来表示允许任意 Header。 |
|
||||
| allow_credentials | bool | 选填 | false | 是否允许跨域访问的请求方携带凭据(如 Cookie 等)。根据 CORS 规范,如果设置该选项为 true,在 allow_origins 不能使用 *, 替换成使用 allow_origin_patterns * |
|
||||
| max_age | number | 选填 | 86400秒 | 浏览器缓存 CORS 结果的最大时间,单位为秒。<br/>在这个时间范围内,浏览器会复用上一次的检查结果 |
|
||||
|
||||
> 注意
|
||||
> * allow_credentials 是一个很敏感的选项,请谨慎开启。开启之后,allow_credentials 和 allow_origins 为 * 不能同时使用,同时设置时, allow_origins 值为 "*" 生效。
|
||||
> * allow_origins 和 allow_origin_patterns 可以同时设置, 先检查 allow_origins 是否匹配,然后再检查 allow_origin_patterns 是否匹配
|
||||
> * 非法 CORS 请求, HTTP 状态码返回是 403, 返回体内容为 "Invalid CORS request"
|
||||
|
||||
# 配置示例
|
||||
|
||||
## 允许所有跨域访问, 不允许请求方携带凭据
|
||||
```yaml
|
||||
allow_origins:
|
||||
- '*'
|
||||
allow_methods:
|
||||
- '*'
|
||||
allow_headers:
|
||||
- '*'
|
||||
expose_headers:
|
||||
- '*'
|
||||
allow_credentials: false
|
||||
max_age: 7200
|
||||
```
|
||||
|
||||
## 允许所有跨域访问,同时允许请求方携带凭据
|
||||
```yaml
|
||||
allow_origin_patterns:
|
||||
- '*'
|
||||
allow_methods:
|
||||
- '*'
|
||||
allow_headers:
|
||||
- '*'
|
||||
expose_headers:
|
||||
- '*'
|
||||
allow_credentials: true
|
||||
max_age: 7200
|
||||
```
|
||||
|
||||
## 允许特定子域,特定方法,特定请求头跨域访问,同时允许请求方携带凭据
|
||||
```yaml
|
||||
allow_origin_patterns:
|
||||
- http://*.example.com
|
||||
- http://*.example.org:[8080,9090]
|
||||
allow_methods:
|
||||
- GET
|
||||
- PUT
|
||||
- POST
|
||||
- DELETE
|
||||
allow_headers:
|
||||
- Token
|
||||
- Content-Type
|
||||
- Authorization
|
||||
expose_headers:
|
||||
- '*'
|
||||
allow_credentials: true
|
||||
max_age: 7200
|
||||
```
|
||||
|
||||
# 测试
|
||||
|
||||
## 测试配置
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.higress.io/v1
|
||||
kind: McpBridge
|
||||
metadata:
|
||||
name: mcp-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
registries:
|
||||
- domain: httpbin.org
|
||||
name: httpbin
|
||||
port: 80
|
||||
type: dns
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/destination: httpbin.dns
|
||||
higress.io/upstream-vhost: "httpbin.org"
|
||||
higress.io/backend-protocol: HTTP
|
||||
name: ingress-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: httpbin.example.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
resource:
|
||||
apiGroup: networking.higress.io
|
||||
kind: McpBridge
|
||||
name: mcp-cors-httpbin
|
||||
path: /
|
||||
pathType: Prefix
|
||||
---
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: wasm-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
defaultConfigDisable: true
|
||||
matchRules:
|
||||
- config:
|
||||
allow_origins:
|
||||
- http://httpbin.example.net
|
||||
allow_origin_patterns:
|
||||
- http://*.example.com:[*]
|
||||
- http://*.example.org:[9090,8080]
|
||||
allow_methods:
|
||||
- GET
|
||||
- POST
|
||||
- PATCH
|
||||
allow_headers:
|
||||
- Content-Type
|
||||
- Token
|
||||
- Authorization
|
||||
expose_headers:
|
||||
- X-Custom-Header
|
||||
- X-Env-UTM
|
||||
allow_credentials: true
|
||||
max_age: 3600
|
||||
configDisable: false
|
||||
ingress:
|
||||
- ingress-cors-httpbin
|
||||
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/cors:1.0.0
|
||||
imagePullPolicy: Always
|
||||
```
|
||||
|
||||
## 请求测试
|
||||
|
||||
### 简单请求
|
||||
```shell
|
||||
curl -v -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
< HTTP/1.1 200 OK
|
||||
> x-cors-version: 1.0.0
|
||||
> access-control-allow-origin: http://httpbin2.example.org:9090
|
||||
> access-control-expose-headers: X-Custom-Header,X-Env-UTM
|
||||
> access-control-allow-credentials: true
|
||||
```
|
||||
|
||||
### 预检请求
|
||||
|
||||
```shell
|
||||
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Content-Type, Token" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
< HTTP/1.1 200 OK
|
||||
< x-cors-version: 1.0.0
|
||||
< access-control-allow-origin: http://httpbin2.example.org:9090
|
||||
< access-control-allow-methods: GET,POST,PATCH
|
||||
< access-control-allow-headers: Content-Type,Token,Authorization
|
||||
< access-control-expose-headers: X-Custom-Header,X-Env-UTM
|
||||
< access-control-allow-credentials: true
|
||||
< access-control-max-age: 3600
|
||||
< date: Tue, 23 May 2023 11:41:28 GMT
|
||||
< server: istio-envoy
|
||||
< content-length: 0
|
||||
<
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
* Closing connection 0
|
||||
```
|
||||
|
||||
### 非法 CORS Origin 预检请求
|
||||
|
||||
```shell
|
||||
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: GET" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
HTTP/1.1 403 Forbidden
|
||||
< content-length: 70
|
||||
< content-type: text/plain
|
||||
< x-cors-version: 1.0.0
|
||||
< date: Tue, 23 May 2023 11:27:01 GMT
|
||||
< server: istio-envoy
|
||||
<
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
Invalid CORS request
|
||||
```
|
||||
|
||||
### 非法 CORS Method 预检请求
|
||||
|
||||
```shell
|
||||
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: DELETE" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
< HTTP/1.1 403 Forbidden
|
||||
< content-length: 49
|
||||
< content-type: text/plain
|
||||
< x-cors-version: 1.0.0
|
||||
< date: Tue, 23 May 2023 11:28:51 GMT
|
||||
< server: istio-envoy
|
||||
<
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
Invalid CORS request
|
||||
```
|
||||
|
||||
### 非法 CORS Header 预检请求
|
||||
|
||||
```shell
|
||||
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: GET" -H "Access-Control-Request-Headers: TokenView" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
< HTTP/1.1 403 Forbidden
|
||||
< content-length: 52
|
||||
< content-type: text/plain
|
||||
< x-cors-version: 1.0.0
|
||||
< date: Tue, 23 May 2023 11:31:03 GMT
|
||||
< server: istio-envoy
|
||||
<
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
Invalid CORS request
|
||||
```
|
||||
|
||||
# 参考文档
|
||||
- https://www.ruanyifeng.com/blog/2016/04/cors.html
|
||||
- https://fetch.spec.whatwg.org/#http-cors-protocol
|
||||
1
plugins/wasm-go/extensions/cors/VERSION
Normal file
1
plugins/wasm-go/extensions/cors/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
457
plugins/wasm-go/extensions/cors/config/cors_config.go
Normal file
457
plugins/wasm-go/extensions/cors/config/cors_config.go
Normal file
@@ -0,0 +1,457 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMatchAll = "*"
|
||||
defaultAllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS"
|
||||
defaultAllAllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS, HEAD, TRACE, CONNECT"
|
||||
defaultAllowHeaders = "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With," +
|
||||
"If-Modified-Since,Cache-Control,Content-Type,Authorization"
|
||||
defaultMaxAge = 86400
|
||||
protocolHttpName = "http"
|
||||
protocolHttpPort = "80"
|
||||
protocolHttpsName = "https"
|
||||
protocolHttpsPort = "443"
|
||||
|
||||
HeaderPluginDebug = "X-Cors-Version"
|
||||
HeaderPluginTrace = "X-Cors-Trace"
|
||||
HeaderOrigin = "Origin"
|
||||
HttpMethodOptions = "OPTIONS"
|
||||
|
||||
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
||||
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
|
||||
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
||||
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
||||
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
||||
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
|
||||
|
||||
HeaderControlRequestMethod = "Access-Control-Request-Method"
|
||||
HeaderControlRequestHeaders = "Access-Control-Request-Headers"
|
||||
|
||||
HttpContextKey = "CORS"
|
||||
)
|
||||
|
||||
var portsRegex = regexp.MustCompile(`(.*):\[(\*|\d+(,\d+)*)]`)
|
||||
|
||||
type OriginPattern struct {
|
||||
declaredPattern string
|
||||
pattern *regexp.Regexp
|
||||
patternValue string
|
||||
}
|
||||
|
||||
func newOriginPatternFromString(declaredPattern string) OriginPattern {
|
||||
declaredPattern = strings.ToLower(strings.TrimSuffix(declaredPattern, "/"))
|
||||
matches := portsRegex.FindAllStringSubmatch(declaredPattern, -1)
|
||||
portList := ""
|
||||
patternValue := declaredPattern
|
||||
if len(matches) > 0 {
|
||||
patternValue = matches[0][1]
|
||||
portList = matches[0][2]
|
||||
}
|
||||
|
||||
patternValue = "\\Q" + patternValue + "\\E"
|
||||
patternValue = strings.ReplaceAll(patternValue, "*", "\\E.*\\Q")
|
||||
if len(portList) > 0 {
|
||||
if portList == defaultMatchAll {
|
||||
patternValue += "(:\\d+)?"
|
||||
} else {
|
||||
patternValue += ":(" + strings.ReplaceAll(portList, ",", "|") + ")"
|
||||
}
|
||||
}
|
||||
|
||||
return OriginPattern{
|
||||
declaredPattern: declaredPattern,
|
||||
patternValue: patternValue,
|
||||
pattern: regexp.MustCompile(patternValue),
|
||||
}
|
||||
}
|
||||
|
||||
type CorsConfig struct {
|
||||
// allowOrigins A list of origins for which cross-origin requests are allowed.
|
||||
// Be a specific domain, e.g. "https://example.com", or the CORS defined special value "*" for all origins.
|
||||
// Keep in mind however that the CORS spec does not allow "*" when allowCredentials is set to true, using allowOriginPatterns instead
|
||||
// By default, it is set to "*" when allowOriginPatterns is not set too.
|
||||
allowOrigins []string
|
||||
|
||||
// allowOriginPatterns A list of origin patterns for which cross-origin requests are allowed
|
||||
// origins patterns with "*" anywhere in the host name in addition to port
|
||||
// lists Examples:
|
||||
// https://*.example.com -- domains ending with example.com
|
||||
// https://*.example.com:[8080,9090] -- domains ending with example.com on port 8080 or port 9090
|
||||
// https://*.example.com:[*] -- domains ending with example.com on any port, including the default port
|
||||
// The special value "*" allows all origins
|
||||
// By default, it is not set.
|
||||
allowOriginPatterns []OriginPattern
|
||||
|
||||
// allowMethods A list of method for which cross-origin requests are allowed
|
||||
// The special value "*" allows all methods.
|
||||
// By default, it is set to "GET, PUT, POST, DELETE, PATCH, OPTIONS".
|
||||
allowMethods []string
|
||||
|
||||
// allowHeaders A list of headers that a pre-flight request can list as allowed
|
||||
// The special value "*" allows actual requests to send any header
|
||||
// By default, it is set to "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
|
||||
allowHeaders []string
|
||||
|
||||
// exposeHeaders A list of response headers an actual response might have and can be exposed.
|
||||
// The special value "*" allows all headers to be exposed for non-credentialed requests.
|
||||
// By default, it is not set
|
||||
exposeHeaders []string
|
||||
|
||||
// allowCredentials Whether user credentials are supported.
|
||||
// By default, it is not set (i.e. user credentials are not supported).
|
||||
allowCredentials bool
|
||||
|
||||
// maxAge Configure how long, in seconds, the response from a pre-flight request can be cached by clients.
|
||||
// By default, it is set to 86400 seconds.
|
||||
maxAge int
|
||||
}
|
||||
|
||||
type HttpCorsContext struct {
|
||||
IsValid bool
|
||||
ValidReason string
|
||||
IsPreFlight bool
|
||||
IsCorsRequest bool
|
||||
AllowOrigin string
|
||||
AllowMethods string
|
||||
AllowHeaders string
|
||||
ExposeHeaders string
|
||||
AllowCredentials bool
|
||||
MaxAge int
|
||||
}
|
||||
|
||||
func (c *CorsConfig) GetVersion() string {
|
||||
return "1.0.0"
|
||||
}
|
||||
|
||||
func (c *CorsConfig) FillDefaultValues() {
|
||||
if len(c.allowOrigins) == 0 && len(c.allowOriginPatterns) == 0 && c.allowCredentials == false {
|
||||
c.allowOrigins = []string{defaultMatchAll}
|
||||
}
|
||||
if len(c.allowHeaders) == 0 {
|
||||
c.allowHeaders = []string{defaultAllowHeaders}
|
||||
}
|
||||
if len(c.allowMethods) == 0 {
|
||||
c.allowMethods = strings.Split(defaultAllowMethods, ",")
|
||||
}
|
||||
if c.maxAge == 0 {
|
||||
c.maxAge = defaultMaxAge
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddAllowOrigin(origin string) error {
|
||||
origin = strings.TrimSpace(origin)
|
||||
if len(origin) == 0 {
|
||||
return nil
|
||||
}
|
||||
if origin == defaultMatchAll {
|
||||
if c.allowCredentials == true {
|
||||
return errors.New("can't set origin to * when allowCredentials is true, use AllowOriginPatterns instead")
|
||||
}
|
||||
c.allowOrigins = []string{defaultMatchAll}
|
||||
return nil
|
||||
}
|
||||
c.allowOrigins = append(c.allowOrigins, strings.TrimSuffix(origin, "/"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddAllowHeader(header string) {
|
||||
header = strings.TrimSpace(header)
|
||||
if len(header) == 0 {
|
||||
return
|
||||
}
|
||||
if header == defaultMatchAll {
|
||||
c.allowHeaders = []string{defaultMatchAll}
|
||||
return
|
||||
}
|
||||
c.allowHeaders = append(c.allowHeaders, header)
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddAllowMethod(method string) {
|
||||
method = strings.TrimSpace(method)
|
||||
if len(method) == 0 {
|
||||
return
|
||||
}
|
||||
if method == defaultMatchAll {
|
||||
c.allowMethods = []string{defaultMatchAll}
|
||||
return
|
||||
}
|
||||
c.allowMethods = append(c.allowMethods, strings.ToUpper(method))
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddExposeHeader(header string) {
|
||||
header = strings.TrimSpace(header)
|
||||
if len(header) == 0 {
|
||||
return
|
||||
}
|
||||
if header == defaultMatchAll {
|
||||
c.exposeHeaders = []string{defaultMatchAll}
|
||||
return
|
||||
}
|
||||
c.exposeHeaders = append(c.exposeHeaders, header)
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddAllowOriginPattern(pattern string) {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if len(pattern) == 0 {
|
||||
return
|
||||
}
|
||||
originPattern := newOriginPatternFromString(pattern)
|
||||
c.allowOriginPatterns = append(c.allowOriginPatterns, originPattern)
|
||||
}
|
||||
|
||||
func (c *CorsConfig) SetAllowCredentials(allowCredentials bool) error {
|
||||
if allowCredentials && len(c.allowOrigins) > 0 && c.allowOrigins[0] == defaultMatchAll {
|
||||
return errors.New("can't set allowCredentials to true when allowOrigin is *")
|
||||
}
|
||||
c.allowCredentials = allowCredentials
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CorsConfig) SetMaxAge(maxAge int) {
|
||||
if maxAge <= 0 {
|
||||
c.maxAge = defaultMaxAge
|
||||
} else {
|
||||
c.maxAge = maxAge
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CorsConfig) Process(scheme string, host string, method string, headers [][2]string) (HttpCorsContext, error) {
|
||||
scheme = strings.ToLower(strings.TrimSpace(scheme))
|
||||
host = strings.ToLower(strings.TrimSpace(host))
|
||||
method = strings.ToLower(strings.TrimSpace(method))
|
||||
|
||||
// Init httpCorsContext with default values
|
||||
httpCorsContext := HttpCorsContext{IsValid: true, IsPreFlight: false, IsCorsRequest: false, AllowCredentials: false, MaxAge: 0}
|
||||
|
||||
// Get request origin, controlRequestMethod, controlRequestHeaders from http headers
|
||||
origin := ""
|
||||
controlRequestMethod := ""
|
||||
controlRequestHeaders := ""
|
||||
for _, header := range headers {
|
||||
key := header[0]
|
||||
// Get origin
|
||||
if strings.ToLower(key) == strings.ToLower(HeaderOrigin) {
|
||||
origin = strings.TrimSuffix(strings.TrimSpace(header[1]), "/")
|
||||
}
|
||||
// Get control request method & headers
|
||||
if strings.ToLower(key) == strings.ToLower(HeaderControlRequestMethod) {
|
||||
controlRequestMethod = strings.TrimSpace(header[1])
|
||||
}
|
||||
if strings.ToLower(key) == strings.ToLower(HeaderControlRequestHeaders) {
|
||||
controlRequestHeaders = strings.TrimSpace(header[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse if request is CORS and pre-flight request.
|
||||
isCorsRequest := c.isCorsRequest(scheme, host, origin)
|
||||
isPreFlight := c.isPreFlight(origin, method, controlRequestMethod)
|
||||
httpCorsContext.IsCorsRequest = isCorsRequest
|
||||
httpCorsContext.IsPreFlight = isPreFlight
|
||||
|
||||
// Skip when it is not CORS request
|
||||
if !isCorsRequest {
|
||||
httpCorsContext.IsValid = true
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
// Check origin
|
||||
allowOrigin, originOk := c.checkOrigin(origin)
|
||||
if !originOk {
|
||||
// Reject: origin is not allowed
|
||||
httpCorsContext.IsValid = false
|
||||
httpCorsContext.ValidReason = fmt.Sprintf("origin:%s is not allowed", origin)
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
// Check method
|
||||
requestMethod := method
|
||||
if isPreFlight {
|
||||
requestMethod = controlRequestMethod
|
||||
}
|
||||
allowMethods, methodOk := c.checkMethods(requestMethod)
|
||||
if !methodOk {
|
||||
// Reject: method is not allowed
|
||||
httpCorsContext.IsValid = false
|
||||
httpCorsContext.ValidReason = fmt.Sprintf("method:%s is not allowed", requestMethod)
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
// Check headers
|
||||
allowHeaders, headerOK := c.checkHeaders(controlRequestHeaders)
|
||||
|
||||
if isPreFlight && !headerOK {
|
||||
// Reject: headers are not allowed
|
||||
httpCorsContext.IsValid = false
|
||||
httpCorsContext.ValidReason = "Reject: headers are not allowed"
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
// Store result in httpCorsContext and return it.
|
||||
httpCorsContext.AllowOrigin = allowOrigin
|
||||
if isPreFlight {
|
||||
httpCorsContext.AllowMethods = allowMethods
|
||||
}
|
||||
if isPreFlight && len(allowHeaders) > 0 {
|
||||
httpCorsContext.AllowHeaders = allowHeaders
|
||||
}
|
||||
if isPreFlight && c.maxAge > 0 {
|
||||
httpCorsContext.MaxAge = c.maxAge
|
||||
}
|
||||
if len(c.exposeHeaders) > 0 {
|
||||
httpCorsContext.ExposeHeaders = strings.Join(c.exposeHeaders, ",")
|
||||
}
|
||||
httpCorsContext.AllowCredentials = c.allowCredentials
|
||||
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
func (c *CorsConfig) checkOrigin(origin string) (string, bool) {
|
||||
origin = strings.TrimSpace(origin)
|
||||
if len(origin) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
matchOrigin := strings.ToLower(origin)
|
||||
// Check exact match
|
||||
for _, allowOrigin := range c.allowOrigins {
|
||||
if allowOrigin == defaultMatchAll {
|
||||
return origin, true
|
||||
}
|
||||
if strings.ToLower(allowOrigin) == matchOrigin {
|
||||
return origin, true
|
||||
}
|
||||
}
|
||||
|
||||
// Check pattern match
|
||||
for _, allowOriginPattern := range c.allowOriginPatterns {
|
||||
if allowOriginPattern.declaredPattern == defaultMatchAll || allowOriginPattern.pattern.MatchString(matchOrigin) {
|
||||
return origin, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (c *CorsConfig) checkHeaders(requestHeaders string) (string, bool) {
|
||||
if len(c.allowHeaders) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if len(requestHeaders) == 0 {
|
||||
return strings.Join(c.allowHeaders, ","), true
|
||||
}
|
||||
|
||||
// Return all request headers when allowHeaders contains *
|
||||
if c.allowHeaders[0] == defaultMatchAll {
|
||||
return requestHeaders, true
|
||||
}
|
||||
|
||||
checkHeaders := strings.Split(requestHeaders, ",")
|
||||
// Each request header should be existed in allowHeaders configuration
|
||||
for _, h := range checkHeaders {
|
||||
isExist := false
|
||||
for _, allowHeader := range c.allowHeaders {
|
||||
if strings.ToLower(h) == strings.ToLower(allowHeader) {
|
||||
isExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isExist {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(c.allowHeaders, ","), true
|
||||
}
|
||||
|
||||
func (c *CorsConfig) checkMethods(requestMethod string) (string, bool) {
|
||||
if len(requestMethod) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Find method existed in allowMethods configuration
|
||||
for _, method := range c.allowMethods {
|
||||
if method == defaultMatchAll {
|
||||
return defaultAllAllowMethods, true
|
||||
}
|
||||
if strings.ToLower(method) == strings.ToLower(requestMethod) {
|
||||
return strings.Join(c.allowMethods, ","), true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (c *CorsConfig) isPreFlight(origin, method, controllerRequestMethod string) bool {
|
||||
return len(origin) > 0 && strings.ToLower(method) == strings.ToLower(HttpMethodOptions) && len(controllerRequestMethod) > 0
|
||||
}
|
||||
|
||||
func (c *CorsConfig) isCorsRequest(scheme, host, origin string) bool {
|
||||
if len(origin) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
url, err := url.Parse(strings.TrimSpace(origin))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check scheme
|
||||
if strings.ToLower(scheme) != strings.ToLower(url.Scheme) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check host and port
|
||||
port := ""
|
||||
originPort := ""
|
||||
originHost := ""
|
||||
host, port = c.getHostAndPort(scheme, host)
|
||||
originHost, originPort = c.getHostAndPort(url.Scheme, url.Host)
|
||||
if host != originHost || port != originPort {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *CorsConfig) getHostAndPort(scheme string, host string) (string, string) {
|
||||
// Get host and port
|
||||
scheme = strings.ToLower(scheme)
|
||||
host = strings.ToLower(host)
|
||||
port := ""
|
||||
hosts := strings.Split(host, ":")
|
||||
if len(hosts) > 1 {
|
||||
host = hosts[0]
|
||||
port = hosts[1]
|
||||
}
|
||||
// Get default port according scheme
|
||||
if len(port) == 0 && scheme == protocolHttpName {
|
||||
port = protocolHttpPort
|
||||
}
|
||||
if len(port) == 0 && scheme == protocolHttpsName {
|
||||
port = protocolHttpsPort
|
||||
}
|
||||
return host, port
|
||||
}
|
||||
408
plugins/wasm-go/extensions/cors/config/cors_config_test.go
Normal file
408
plugins/wasm-go/extensions/cors/config/cors_config_test.go
Normal file
@@ -0,0 +1,408 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCorsConfig_getHostAndPort(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scheme string
|
||||
host string
|
||||
wantHost string
|
||||
wantPort string
|
||||
}{
|
||||
{
|
||||
name: "http without port",
|
||||
scheme: "http",
|
||||
host: "http.example.com",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "80",
|
||||
},
|
||||
{
|
||||
name: "https without port",
|
||||
scheme: "https",
|
||||
host: "http.example.com",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "443",
|
||||
},
|
||||
|
||||
{
|
||||
name: "http with port and case insensitive",
|
||||
scheme: "hTTp",
|
||||
host: "hTTp.Example.com:8080",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "8080",
|
||||
},
|
||||
|
||||
{
|
||||
name: "https with port and case insensitive",
|
||||
scheme: "hTTps",
|
||||
host: "hTTp.Example.com:8080",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "8080",
|
||||
},
|
||||
|
||||
{
|
||||
name: "protocal is not http",
|
||||
scheme: "wss",
|
||||
host: "hTTp.Example.com",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "",
|
||||
},
|
||||
|
||||
{
|
||||
name: "protocal is not http",
|
||||
scheme: "wss",
|
||||
host: "hTTp.Example.com:8080",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "8080",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{}
|
||||
host, port := c.getHostAndPort(tt.scheme, tt.host)
|
||||
assert.Equal(t, tt.wantHost, host)
|
||||
assert.Equal(t, tt.wantPort, port)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_isCorsRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
scheme string
|
||||
host string
|
||||
origin string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "blank origin",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "normal equal case with space and case ",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: "http://hTTPbin.Example.com",
|
||||
want: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "cors request with port diff",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: " http://httpbin.example.com:8080 ",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "cors request with scheme diff",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: " https://HTTPpbin.Example.com ",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "cors request with host diff",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: " http://HTTPpbin.Example.org ",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{}
|
||||
assert.Equalf(t, tt.want, c.isCorsRequest(tt.scheme, tt.host, tt.origin), "isCorsRequest(%v, %v, %v)", tt.scheme, tt.host, tt.origin)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_isPreFlight(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
origin string
|
||||
method string
|
||||
controllerRequestMethod string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "blank case",
|
||||
origin: "",
|
||||
method: "",
|
||||
controllerRequestMethod: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "normal case",
|
||||
origin: "http://httpbin.example.com",
|
||||
method: "Options",
|
||||
controllerRequestMethod: "PUT",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "bad case with diff method",
|
||||
origin: "http://httpbin.example.com",
|
||||
method: "GET",
|
||||
controllerRequestMethod: "PUT",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{}
|
||||
assert.Equalf(t, tt.want, c.isPreFlight(tt.origin, tt.method, tt.controllerRequestMethod), "isPreFlight(%v, %v, %v)", tt.origin, tt.method, tt.controllerRequestMethod)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_checkMethods(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
allowMethods []string
|
||||
requestMethod string
|
||||
wantMethods string
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "default *",
|
||||
allowMethods: []string{"*"},
|
||||
requestMethod: "GET",
|
||||
wantMethods: defaultAllAllowMethods,
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "normal allow case",
|
||||
allowMethods: []string{"GET", "PUT", "HEAD"},
|
||||
requestMethod: "get",
|
||||
wantMethods: "GET,PUT,HEAD",
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "forbidden case",
|
||||
allowMethods: []string{"GET", "PUT", "HEAD"},
|
||||
requestMethod: "POST",
|
||||
wantMethods: "",
|
||||
wantOk: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "blank method",
|
||||
allowMethods: []string{"GET", "PUT", "HEAD"},
|
||||
requestMethod: "",
|
||||
wantMethods: "",
|
||||
wantOk: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{
|
||||
allowMethods: tt.allowMethods,
|
||||
}
|
||||
allowMethods, allowOk := c.checkMethods(tt.requestMethod)
|
||||
assert.Equalf(t, tt.wantMethods, allowMethods, "checkMethods(%v)", tt.requestMethod)
|
||||
assert.Equalf(t, tt.wantOk, allowOk, "checkMethods(%v)", tt.requestMethod)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_checkHeaders(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
allowHeaders []string
|
||||
requestHeaders string
|
||||
wantHeaders string
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "not pre-flight",
|
||||
allowHeaders: []string{"Content-Type", "Authorization"},
|
||||
requestHeaders: "",
|
||||
wantHeaders: "Content-Type,Authorization",
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "blank allowheaders case 1",
|
||||
allowHeaders: []string{},
|
||||
requestHeaders: "",
|
||||
wantHeaders: "",
|
||||
wantOk: false,
|
||||
},
|
||||
{
|
||||
name: "blank allowheaders case 2",
|
||||
requestHeaders: "Authorization",
|
||||
wantHeaders: "",
|
||||
wantOk: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "allowheaders *",
|
||||
allowHeaders: []string{"*"},
|
||||
requestHeaders: "Content-Type,Authorization",
|
||||
wantHeaders: "Content-Type,Authorization",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "allowheader values 1",
|
||||
allowHeaders: []string{"Content-Type", "Authorization"},
|
||||
requestHeaders: "Content-Type,Authorization",
|
||||
wantHeaders: "Content-Type,Authorization",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "allowheader values 2",
|
||||
allowHeaders: []string{"Content-Type", "Authorization"},
|
||||
requestHeaders: "",
|
||||
wantHeaders: "Content-Type,Authorization",
|
||||
wantOk: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{
|
||||
allowHeaders: tt.allowHeaders,
|
||||
}
|
||||
allowHeaders, allowOk := c.checkHeaders(tt.requestHeaders)
|
||||
assert.Equalf(t, tt.wantHeaders, allowHeaders, "checkHeaders(%v)", tt.requestHeaders)
|
||||
assert.Equalf(t, tt.wantOk, allowOk, "checkHeaders(%v)", tt.requestHeaders)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_checkOrigin(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
allowOrigins []string
|
||||
allowOriginPatterns []OriginPattern
|
||||
origin string
|
||||
wantOrigin string
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "allowOrigins *",
|
||||
allowOrigins: []string{defaultMatchAll},
|
||||
allowOriginPatterns: []OriginPattern{},
|
||||
origin: "http://Httpbin.Example.COM",
|
||||
wantOrigin: "http://Httpbin.Example.COM",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "allowOrigins exact match case 1",
|
||||
allowOrigins: []string{"http://httpbin.example.com"},
|
||||
allowOriginPatterns: []OriginPattern{},
|
||||
origin: "http://HTTPBin.EXample.COM",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM",
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "allowOrigins exact match case 2",
|
||||
allowOrigins: []string{"https://httpbin.example.com"},
|
||||
allowOriginPatterns: []OriginPattern{},
|
||||
origin: "http://HTTPBin.EXample.COM",
|
||||
wantOrigin: "",
|
||||
wantOk: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match with *",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("*"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match case with any port",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[*]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM",
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "OriginPattern pattern match case with any port",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[*]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM:10000",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM:10000",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match case with specail port 1",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM:10000",
|
||||
wantOrigin: "",
|
||||
wantOk: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match case with specail port 2",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM:9090",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM:9090",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match case with specail port 3",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.org:9090",
|
||||
wantOrigin: "",
|
||||
wantOk: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{
|
||||
allowOrigins: tt.allowOrigins,
|
||||
allowOriginPatterns: tt.allowOriginPatterns,
|
||||
}
|
||||
allowOrigin, allowOk := c.checkOrigin(tt.origin)
|
||||
assert.Equalf(t, tt.wantOrigin, allowOrigin, "checkOrigin(%v)", tt.origin)
|
||||
assert.Equalf(t, tt.wantOk, allowOk, "checkOrigin(%v)", tt.origin)
|
||||
})
|
||||
}
|
||||
}
|
||||
67
plugins/wasm-go/extensions/cors/cors.yaml
Normal file
67
plugins/wasm-go/extensions/cors/cors.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
apiVersion: networking.higress.io/v1
|
||||
kind: McpBridge
|
||||
metadata:
|
||||
name: mcp-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
registries:
|
||||
- domain: httpbin.org
|
||||
name: httpbin
|
||||
port: 80
|
||||
type: dns
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/destination: httpbin.dns
|
||||
higress.io/upstream-vhost: "httpbin.org"
|
||||
higress.io/backend-protocol: HTTP
|
||||
name: ingress-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: httpbin.example.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
resource:
|
||||
apiGroup: networking.higress.io
|
||||
kind: McpBridge
|
||||
name: mcp-cors-httpbin
|
||||
path: /
|
||||
pathType: Prefix
|
||||
---
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: wasm-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
defaultConfigDisable: true
|
||||
matchRules:
|
||||
- config:
|
||||
allow_origins:
|
||||
- http://httpbin.example.net
|
||||
allow_origin_patterns:
|
||||
- http://*.example.com:[*]
|
||||
- http://*.example.org:[9090,8080]
|
||||
allow_methods:
|
||||
- GET
|
||||
- POST
|
||||
- PATCH
|
||||
allow_headers:
|
||||
- Content-Type
|
||||
- Token
|
||||
- Authorization
|
||||
expose_headers:
|
||||
- X-Custom-Header
|
||||
- X-Env-UTM
|
||||
allow_credentials: true
|
||||
max_age: 3600
|
||||
configDisable: false
|
||||
ingress:
|
||||
- ingress-cors-httpbin
|
||||
url: oci://docker.io/2456868764/cors:1.0.0
|
||||
imagePullPolicy: Always
|
||||
78
plugins/wasm-go/extensions/cors/envoy.yaml
Normal file
78
plugins/wasm-go/extensions/cors/envoy.yaml
Normal file
@@ -0,0 +1,78 @@
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: main
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 18000
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
stat_prefix: ingress_http
|
||||
codec_type: auto
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains:
|
||||
- "httpbin.example.com"
|
||||
routes:
|
||||
- match:
|
||||
prefix: "/"
|
||||
route:
|
||||
cluster: httpbin
|
||||
http_filters:
|
||||
- name: envoy.filters.http.wasm
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
|
||||
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
|
||||
value:
|
||||
config:
|
||||
configuration:
|
||||
"@type": type.googleapis.com/google.protobuf.StringValue
|
||||
value: |-
|
||||
{
|
||||
"allow_origins": ["http://httpbin.example.net"],
|
||||
"allow_origin_patterns": ["http://*.example.com:[*]", "http://*.example.org:[9090,8080]"],
|
||||
"allow_methods": ["GET","PUT","POST", "PATCH", "HEAD", "OPTIONS"],
|
||||
"allow_credentials": true,
|
||||
"allow_headers":["Content-Type", "Token","Authorization"],
|
||||
"expose_headers":["X-Custom-Header"],
|
||||
"max_age": 3600
|
||||
}
|
||||
vm_config:
|
||||
runtime: "envoy.wasm.runtime.v8"
|
||||
code:
|
||||
local:
|
||||
filename: "./main.wasm"
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
|
||||
|
||||
clusters:
|
||||
- name: httpbin
|
||||
connect_timeout: 0.5s
|
||||
type: STRICT_DNS
|
||||
lb_policy: ROUND_ROBIN
|
||||
dns_refresh_rate: 5s
|
||||
dns_lookup_family: V4_ONLY
|
||||
load_assignment:
|
||||
cluster_name: httpbin
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: httpbin.org
|
||||
port_value: 80
|
||||
|
||||
|
||||
admin:
|
||||
access_log_path: "/dev/null"
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 8001
|
||||
21
plugins/wasm-go/extensions/cors/go.mod
Normal file
21
plugins/wasm-go/extensions/cors/go.mod
Normal file
@@ -0,0 +1,21 @@
|
||||
module cors
|
||||
|
||||
go 1.19
|
||||
|
||||
replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230519024024-625c06e58f91
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
20
plugins/wasm-go/extensions/cors/go.sum
Normal file
20
plugins/wasm-go/extensions/cors/go.sum
Normal file
@@ -0,0 +1,20 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
169
plugins/wasm-go/extensions/cors/main.go
Normal file
169
plugins/wasm-go/extensions/cors/main.go
Normal file
@@ -0,0 +1,169 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"cors/config"
|
||||
"fmt"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wrapper.SetCtx(
|
||||
"cors",
|
||||
wrapper.ParseConfigBy(parseConfig),
|
||||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
|
||||
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
|
||||
wrapper.ProcessResponseBodyBy(onHttpResponseBody),
|
||||
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
|
||||
)
|
||||
}
|
||||
|
||||
func parseConfig(json gjson.Result, corsConfig *config.CorsConfig, log wrapper.Log) error {
|
||||
log.Debug("parseConfig()")
|
||||
allowOrigins := json.Get("allow_origins").Array()
|
||||
for _, origin := range allowOrigins {
|
||||
if err := corsConfig.AddAllowOrigin(origin.String()); err != nil {
|
||||
log.Warnf("failed to AddAllowOrigin:%s, error:%v", origin, err)
|
||||
}
|
||||
}
|
||||
allowOriginPatterns := json.Get("allow_origin_patterns").Array()
|
||||
for _, pattern := range allowOriginPatterns {
|
||||
corsConfig.AddAllowOriginPattern(pattern.String())
|
||||
}
|
||||
allowMethods := json.Get("allow_methods").Array()
|
||||
for _, method := range allowMethods {
|
||||
corsConfig.AddAllowMethod(method.String())
|
||||
}
|
||||
allowHeaders := json.Get("allow_headers").Array()
|
||||
for _, header := range allowHeaders {
|
||||
corsConfig.AddAllowHeader(header.String())
|
||||
}
|
||||
exposeHeaders := json.Get("expose_headers").Array()
|
||||
for _, header := range exposeHeaders {
|
||||
corsConfig.AddExposeHeader(header.String())
|
||||
}
|
||||
allowCredentials := json.Get("allow_credentials").Bool()
|
||||
if err := corsConfig.SetAllowCredentials(allowCredentials); err != nil {
|
||||
log.Warnf("failed to set AllowCredentials error: %v", err)
|
||||
}
|
||||
maxAge := json.Get("max_age").Int()
|
||||
corsConfig.SetMaxAge(int(maxAge))
|
||||
|
||||
// Fill default values
|
||||
corsConfig.FillDefaultValues()
|
||||
log.Debugf("corsConfig:%+v", corsConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, corsConfig config.CorsConfig, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpRequestHeaders()")
|
||||
requestUrl, _ := proxywasm.GetHttpRequestHeader(":path")
|
||||
method, _ := proxywasm.GetHttpRequestHeader(":method")
|
||||
host := ctx.Host()
|
||||
scheme := ctx.Scheme()
|
||||
log.Debugf("scheme:%s, host:%s, method:%s, request:%s", scheme, host, method, requestUrl)
|
||||
// Get headers
|
||||
headers, _ := proxywasm.GetHttpRequestHeaders()
|
||||
// Process request
|
||||
httpCorsContext, err := corsConfig.Process(scheme, host, method, headers)
|
||||
if err != nil {
|
||||
log.Warnf("failed to process %s : %v", requestUrl, err)
|
||||
return types.ActionContinue
|
||||
}
|
||||
log.Debugf("Process httpCorsContext:%+v", httpCorsContext)
|
||||
// Set HttpContext
|
||||
ctx.SetContext(config.HttpContextKey, httpCorsContext)
|
||||
|
||||
// Response forbidden when it is not valid cors request
|
||||
if !httpCorsContext.IsValid {
|
||||
headers := make([][2]string, 0)
|
||||
headers = append(headers, [2]string{config.HeaderPluginTrace, "trace"})
|
||||
proxywasm.SendHttpResponse(403, headers, []byte("Invalid CORS request"), -1)
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
// Response directly when it is cors preflight request
|
||||
if httpCorsContext.IsPreFlight {
|
||||
headers := make([][2]string, 0)
|
||||
headers = append(headers, [2]string{config.HeaderPluginTrace, "trace"})
|
||||
proxywasm.SendHttpResponse(200, headers, nil, -1)
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpRequestBody(ctx wrapper.HttpContext, corsConfig config.CorsConfig, body []byte, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpRequestBody()")
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpResponseHeaders(ctx wrapper.HttpContext, corsConfig config.CorsConfig, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpResponseHeaders()")
|
||||
// Remove trace header if existed
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderPluginTrace)
|
||||
// Remove upstream cors response headers if existed
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowOrigin)
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowMethods)
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlExposeHeaders)
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowCredentials)
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlMaxAge)
|
||||
// Add debug header
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderPluginDebug, corsConfig.GetVersion())
|
||||
|
||||
// Restore httpCorsContext from HttpContext
|
||||
httpCorsContext, ok := ctx.GetContext(config.HttpContextKey).(config.HttpCorsContext)
|
||||
if !ok {
|
||||
log.Debug("restore httpCorsContext from HttpContext error")
|
||||
return types.ActionContinue
|
||||
}
|
||||
log.Debugf("Restore httpCorsContext:%+v", httpCorsContext)
|
||||
|
||||
// Skip which it is not valid or not cors request
|
||||
if !httpCorsContext.IsValid || !httpCorsContext.IsCorsRequest {
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// Add Cors headers when it is cors and valid request
|
||||
if len(httpCorsContext.AllowOrigin) > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowOrigin, httpCorsContext.AllowOrigin)
|
||||
}
|
||||
if len(httpCorsContext.AllowMethods) > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowMethods, httpCorsContext.AllowMethods)
|
||||
}
|
||||
if len(httpCorsContext.AllowHeaders) > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowHeaders, httpCorsContext.AllowHeaders)
|
||||
}
|
||||
if len(httpCorsContext.ExposeHeaders) > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlExposeHeaders, httpCorsContext.ExposeHeaders)
|
||||
}
|
||||
if httpCorsContext.AllowCredentials {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowCredentials, "true")
|
||||
}
|
||||
if httpCorsContext.MaxAge > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlMaxAge, fmt.Sprintf("%d", httpCorsContext.MaxAge))
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpResponseBody(ctx wrapper.HttpContext, corsConfig config.CorsConfig, body []byte, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpResponseBody()")
|
||||
return types.ActionContinue
|
||||
}
|
||||
195
plugins/wasm-go/extensions/de-graphql/README.md
Normal file
195
plugins/wasm-go/extensions/de-graphql/README.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# DeGraphQL
|
||||
|
||||
## GraphQL
|
||||
|
||||
### GraphQL 端点
|
||||
|
||||
REST API 有多个端点,GraphQL API 只有一个端点。
|
||||
|
||||
```shell
|
||||
https://api.github.com/graphql
|
||||
```
|
||||
### 与 GraphQL 通信
|
||||
|
||||
由于 GraphQL 操作由多行 JSON 组成,可以使用 curl 或任何其他采用 HTTP 的库。
|
||||
|
||||
在 REST 中,HTTP 谓词确定执行的操作。 在 GraphQL 中,执行查询要提供 JSON 请求体,因此 HTTP 谓词为 POST。 唯一的例外是内省查询,它是一种简单的 GET 到终结点查询。
|
||||
|
||||
### GraphQL POST 请求参数
|
||||
|
||||
标准的 GraphQL POST 请求情况如下:
|
||||
|
||||
- 添加 HTTP 请求头: Content-Type: application/json
|
||||
- 使用 JSON 格式的请求体
|
||||
- JSON 请求体包含三个字段
|
||||
- query:查询文档,必填
|
||||
- variables:变量,选填
|
||||
- operationName:操作名称,选填,查询文档有多个操作时必填
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "{viewer{name}}",
|
||||
"operationName": "",
|
||||
"variables": {
|
||||
"name": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GraphQL 基本参数类型
|
||||
|
||||
- 基本参数类型包含: String, Int, Float, Boolean
|
||||
- [类型]代表数组,例如:[Int]代表整型数组
|
||||
- GraphQL 基本参数传递
|
||||
- 小括号内定义形参,注意:参数需要定义类型
|
||||
- !(叹号)代表参数不能为空
|
||||
|
||||
```shell
|
||||
query ($owner : String!, $name : String!) {
|
||||
repository(owner: $owner, name: $name) {
|
||||
name
|
||||
forkCount
|
||||
description
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### GitHub GraphQL 测试
|
||||
|
||||
使用 curl 命令查询 GraphQL, 用有效 JSON 请求体发出 POST 请求。 有效请求体必须包含一个名为 query 的字符串。
|
||||
|
||||
```shell
|
||||
|
||||
curl https://api.github.com/graphql -X POST \
|
||||
-H "Authorization: bearer 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" \
|
||||
-d "{\"query\": \"query { viewer { login }}\"}"
|
||||
|
||||
{
|
||||
"data": {
|
||||
"viewer": {
|
||||
"login": "2456868764"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
curl 'https://api.github.com/graphql' -X POST \
|
||||
-H 'Authorization: bearer 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' \
|
||||
-d '{"query":"query ($owner: String!, $name: String!) {\n repository(owner: $owner, name: $name) {\n name\n forkCount\n description\n }\n}\n","variables":{"owner":"2456868764","name":"higress"}}'
|
||||
|
||||
{
|
||||
"data": {
|
||||
"repository": {
|
||||
"name": "higress",
|
||||
"forkCount": 149,
|
||||
"description": "Next-generation Cloud Native Gateway | 下一代云原生网关"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## DeGraphQL 插件
|
||||
|
||||
### 参数配置
|
||||
|
||||
| 参数 | 描述 | 默认 |
|
||||
|:----------------|:------------------------|:-----------|
|
||||
| `gql` | graphql 查询 | 不能为空 |
|
||||
| `endpoint` | graphql 查询端点 | `/graphql` |
|
||||
| `timeout` | 查询连接超时,单位毫秒 | `5000` |
|
||||
| `domain` | 服务域名,当服务来源是dns配置 | |
|
||||
|
||||
### 插件使用
|
||||
|
||||
https://github.com/alibaba/higress/issues/268
|
||||
|
||||
- 测试配置
|
||||
```yaml
|
||||
apiVersion: networking.higress.io/v1
|
||||
kind: McpBridge
|
||||
metadata:
|
||||
name: default
|
||||
namespace: higress-system
|
||||
spec:
|
||||
registries:
|
||||
- domain: api.github.com
|
||||
name: github
|
||||
port: 443
|
||||
type: dns
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/destination: github.dns
|
||||
higress.io/upstream-vhost: "api.github.com"
|
||||
higress.io/backend-protocol: HTTPS
|
||||
name: github-api
|
||||
namespace: higress-system
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- backend:
|
||||
resource:
|
||||
apiGroup: networking.higress.io
|
||||
kind: McpBridge
|
||||
name: default
|
||||
path: /api
|
||||
pathType: Prefix
|
||||
---
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: de-graphql-github-api
|
||||
namespace: higress-system
|
||||
spec:
|
||||
matchRules:
|
||||
- ingress:
|
||||
- github-api
|
||||
config:
|
||||
timeout: 5000
|
||||
endpoint: /graphql
|
||||
domain: api.github.com
|
||||
gql: |
|
||||
query ($owner:String! $name:String!){
|
||||
repository(owner:$owner, name:$name) {
|
||||
name
|
||||
forkCount
|
||||
description
|
||||
}
|
||||
}
|
||||
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/de-graphql:1.0.0
|
||||
```
|
||||
|
||||
- 测试结果
|
||||
|
||||
```shell
|
||||
curl "http://localhost/api?owner=alibaba&name=higress" -H "Authorization: Bearer some-token"
|
||||
|
||||
{
|
||||
"data": {
|
||||
"repository": {
|
||||
"description": "Next-generation Cloud Native Gateway",
|
||||
"forkCount": 149,
|
||||
"name": "higress"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 参考文档
|
||||
|
||||
- https://github.com/graphql/graphql-spec
|
||||
- https://docs.github.com/zh/graphql/guides/forming-calls-with-graphql
|
||||
- https://github.com/altair-graphql/altair
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1
plugins/wasm-go/extensions/de-graphql/VERSION
Normal file
1
plugins/wasm-go/extensions/de-graphql/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
198
plugins/wasm-go/extensions/de-graphql/config/degraphql_config.go
Normal file
198
plugins/wasm-go/extensions/de-graphql/config/degraphql_config.go
Normal file
@@ -0,0 +1,198 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultEndpoint string = "/graphql"
|
||||
DefaultConnectionTimeout uint32 = 5000
|
||||
)
|
||||
|
||||
var gqlVariableRegex = regexp.MustCompile(`\$(\w+)\s*:\s*(String|Float|Int|Boolean)(!?)`)
|
||||
|
||||
type VariableType string
|
||||
|
||||
const (
|
||||
StringType VariableType = "String"
|
||||
IntType VariableType = "Int"
|
||||
FloatType VariableType = "Float"
|
||||
BooleanType VariableType = "Boolean"
|
||||
)
|
||||
|
||||
type Variable struct {
|
||||
name string
|
||||
typ VariableType
|
||||
blank bool
|
||||
value string
|
||||
}
|
||||
|
||||
type DeGraphQLConfig struct {
|
||||
gql string
|
||||
endpoint string
|
||||
timeout uint32
|
||||
domain string
|
||||
variables []Variable
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) SetEndpoint(endpoint string) error {
|
||||
endpoint = strings.TrimSpace(endpoint)
|
||||
if endpoint == "" {
|
||||
d.endpoint = DefaultEndpoint
|
||||
} else {
|
||||
d.endpoint = endpoint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) GetDomain() string {
|
||||
return d.domain
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) SetDomain(domain string) {
|
||||
d.domain = domain
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) GetEndpoint() string {
|
||||
return d.endpoint
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) GetTimeout() uint32 {
|
||||
return d.timeout
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) SetTimeout(timeout uint32) {
|
||||
if timeout <= 0 {
|
||||
d.timeout = DefaultConnectionTimeout
|
||||
} else {
|
||||
d.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) SetGql(gql string) error {
|
||||
if strings.TrimSpace(gql) == "" {
|
||||
return errors.New("gql can't be empty")
|
||||
}
|
||||
d.gql = gql
|
||||
d.variables = make([]Variable, 0)
|
||||
matches := gqlVariableRegex.FindAllStringSubmatch(d.gql, -1)
|
||||
if len(matches) > 0 {
|
||||
for _, subMatch := range matches {
|
||||
variable := Variable{}
|
||||
variable.name = subMatch[1]
|
||||
switch subMatch[2] {
|
||||
case "String":
|
||||
variable.typ = StringType
|
||||
case "Float":
|
||||
variable.typ = FloatType
|
||||
case "Int":
|
||||
variable.typ = IntType
|
||||
case "Boolean":
|
||||
variable.typ = BooleanType
|
||||
}
|
||||
variable.blank = subMatch[3] != "!"
|
||||
d.variables = append(d.variables, variable)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) GetGql() string {
|
||||
return d.gql
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) GetVersion() string {
|
||||
return "1.0.0"
|
||||
}
|
||||
|
||||
func (d *DeGraphQLConfig) ParseGqlFromUrl(requestUrl string) (string, error) {
|
||||
if strings.TrimSpace(requestUrl) == "" {
|
||||
return "", errors.New("request url can't be empty")
|
||||
}
|
||||
|
||||
url, _ := url.Parse(requestUrl)
|
||||
|
||||
queryValues := url.Query()
|
||||
values := make(map[string]string, len(queryValues))
|
||||
for k, v := range queryValues {
|
||||
var v1 string
|
||||
if len(v) > 1 {
|
||||
v1 = strings.Join(v, ",")
|
||||
} else {
|
||||
v1 = v[0]
|
||||
}
|
||||
values[k] = v1
|
||||
}
|
||||
|
||||
variables := make([]Variable, 0, len(d.variables))
|
||||
for _, variable := range d.variables {
|
||||
val, ok := values[variable.name]
|
||||
// TODO validate variable type and blank
|
||||
if ok {
|
||||
variables = append(variables, Variable{
|
||||
name: variable.name,
|
||||
typ: variable.typ,
|
||||
blank: variable.blank,
|
||||
value: val,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var build strings.Builder
|
||||
|
||||
// write query
|
||||
build.WriteString("{\"query\":")
|
||||
build.WriteString("\"")
|
||||
build.WriteString(getJsonStr(d.gql))
|
||||
build.WriteString("\"")
|
||||
|
||||
// write varialbes
|
||||
if len(variables) > 0 {
|
||||
index := 0
|
||||
build.WriteString(",")
|
||||
build.WriteString("\"variables\":{")
|
||||
for _, variable := range variables {
|
||||
build.WriteString("\"")
|
||||
build.WriteString(variable.name)
|
||||
build.WriteString("\":")
|
||||
if variable.typ == StringType {
|
||||
build.WriteString("\"")
|
||||
build.WriteString(getJsonStr(variable.value))
|
||||
build.WriteString("\"")
|
||||
} else {
|
||||
build.WriteString(variable.value)
|
||||
}
|
||||
if index < len(variables)-1 {
|
||||
build.WriteString(",")
|
||||
}
|
||||
index++
|
||||
}
|
||||
build.WriteString("}")
|
||||
}
|
||||
|
||||
build.WriteString("}")
|
||||
return build.String(), nil
|
||||
}
|
||||
|
||||
func getJsonStr(str string) string {
|
||||
d := strings.ReplaceAll(str, "\"", "\\\"")
|
||||
return strings.ReplaceAll(d, "\n", "\\n")
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeGraphQLConfig_SetGql(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
gql string
|
||||
wantVariables []Variable
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "empty gql",
|
||||
gql: "",
|
||||
wantErr: errors.New("gql can't be empty"),
|
||||
},
|
||||
{
|
||||
name: "no params",
|
||||
gql: "query",
|
||||
wantVariables: []Variable{},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "four params",
|
||||
gql: "query ($owner:String $num:Float! $int : Int! $boolean : Boolean )",
|
||||
wantErr: nil,
|
||||
wantVariables: []Variable{
|
||||
{
|
||||
name: "owner",
|
||||
typ: StringType,
|
||||
blank: true,
|
||||
},
|
||||
{
|
||||
name: "num",
|
||||
typ: FloatType,
|
||||
blank: false,
|
||||
},
|
||||
{
|
||||
name: "int",
|
||||
typ: IntType,
|
||||
blank: false,
|
||||
},
|
||||
{
|
||||
name: "boolean",
|
||||
typ: BooleanType,
|
||||
blank: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := &DeGraphQLConfig{}
|
||||
err := d.SetGql(tt.gql)
|
||||
assert.Equal(t, tt.wantErr, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.wantVariables, d.variables)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeGraphQLConfig_ParseGqlFromUrl(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
gql string
|
||||
url string
|
||||
want string
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "empty url",
|
||||
gql: "query ($owner:String! $name:String!)",
|
||||
url: "",
|
||||
want: "",
|
||||
wantErr: errors.New("request url can't be empty"),
|
||||
},
|
||||
|
||||
{
|
||||
name: "no params",
|
||||
gql: "query HeroNameQuery {\n hero {\n name\n }\n}",
|
||||
url: "/api?owner=a",
|
||||
want: "{\"query\":\"query HeroNameQuery {\\n hero {\\n name\\n }\\n}\"}",
|
||||
wantErr: nil,
|
||||
},
|
||||
|
||||
{
|
||||
name: "one string variable",
|
||||
gql: "query FetchSomeIDQuery($someId: String!) {\n human(id: $someId) {\n name\n }\n}",
|
||||
url: "/api?someId=a",
|
||||
want: "{\"query\":\"query FetchSomeIDQuery($someId: String!) {\\n human(id: $someId) {\\n name\\n }\\n}\",\"variables\":{\"someId\":\"a\"}}",
|
||||
wantErr: nil,
|
||||
},
|
||||
|
||||
{
|
||||
name: "multi variables",
|
||||
gql: "query FetchSomeIDQuery($someId: String! $num: Int $price: Float! $need:Boolean!) {\n human(id: $someId) {\n name\n }\n}",
|
||||
url: "/api?someId=a&num=10&price=12.0&need=false&hee=1",
|
||||
want: "{\"query\":\"query FetchSomeIDQuery($someId: String! $num: Int $price: Float! $need:Boolean!) {\\n human(id: $someId) {\\n name\\n }\\n}\",\"variables\":{\"someId\":\"a\",\"num\":10,\"price\":12.0,\"need\":false}}",
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := &DeGraphQLConfig{}
|
||||
d.SetGql(tt.gql)
|
||||
body, err := d.ParseGqlFromUrl(tt.url)
|
||||
assert.Equal(t, tt.wantErr, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, body)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeGraphQLConfig_SetEndpoint(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
endPoint string
|
||||
wantErr error
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty endpoint",
|
||||
endPoint: "",
|
||||
wantErr: nil,
|
||||
want: "/graphql",
|
||||
},
|
||||
{
|
||||
name: "empty endpoint with blank",
|
||||
endPoint: " ",
|
||||
wantErr: nil,
|
||||
want: "/graphql",
|
||||
},
|
||||
|
||||
{
|
||||
name: "with value",
|
||||
endPoint: " /graphql2 ",
|
||||
wantErr: nil,
|
||||
want: "/graphql2",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := &DeGraphQLConfig{}
|
||||
err := d.SetEndpoint(tt.endPoint)
|
||||
assert.Equal(t, tt.wantErr, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, d.endpoint)
|
||||
})
|
||||
}
|
||||
}
|
||||
122
plugins/wasm-go/extensions/de-graphql/envoy.yaml
Normal file
122
plugins/wasm-go/extensions/de-graphql/envoy.yaml
Normal file
@@ -0,0 +1,122 @@
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: main
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 18000
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
stat_prefix: ingress_http
|
||||
codec_type: auto
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains:
|
||||
- "*"
|
||||
routes:
|
||||
- match:
|
||||
prefix: "/api"
|
||||
route:
|
||||
cluster: github
|
||||
http_filters:
|
||||
- name: envoy.filters.http.wasm
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
|
||||
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
|
||||
value:
|
||||
config:
|
||||
configuration:
|
||||
"@type": type.googleapis.com/google.protobuf.StringValue
|
||||
value: |-
|
||||
{
|
||||
"gql": "query ($owner:String! $name:String!){\n repository(owner:$owner, name:$name) {\n name\n forkCount\n description\n}\n}",
|
||||
"domain": "api.github.com",
|
||||
"endpoint": "/graphql",
|
||||
"timeout": 2000
|
||||
}
|
||||
vm_config:
|
||||
runtime: "envoy.wasm.runtime.v8"
|
||||
code:
|
||||
local:
|
||||
filename: "./main.wasm"
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
|
||||
|
||||
- name: staticreply
|
||||
address:
|
||||
socket_address:
|
||||
address: 127.0.0.1
|
||||
port_value: 8099
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
stat_prefix: ingress_http
|
||||
codec_type: auto
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains:
|
||||
- "*"
|
||||
routes:
|
||||
- match:
|
||||
prefix: "/"
|
||||
direct_response:
|
||||
status: 200
|
||||
body:
|
||||
inline_string: "example body\n"
|
||||
http_filters:
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
|
||||
clusters:
|
||||
- name: mock_service
|
||||
connect_timeout: 0.25s
|
||||
type: STATIC
|
||||
lb_policy: ROUND_ROBIN
|
||||
load_assignment:
|
||||
cluster_name: mock_service
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: 127.0.0.1
|
||||
port_value: 8099
|
||||
- name: github
|
||||
connect_timeout: 0.5s
|
||||
type: STRICT_DNS
|
||||
lb_policy: ROUND_ROBIN
|
||||
dns_refresh_rate: 5s
|
||||
dns_lookup_family: V4_ONLY
|
||||
transport_socket:
|
||||
name: envoy.transport_sockets.tls
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
|
||||
load_assignment:
|
||||
cluster_name: github
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: api.github.com
|
||||
port_value: 443
|
||||
|
||||
|
||||
admin:
|
||||
access_log_path: "/dev/null"
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 8001
|
||||
22
plugins/wasm-go/extensions/de-graphql/go.mod
Normal file
22
plugins/wasm-go/extensions/de-graphql/go.mod
Normal file
@@ -0,0 +1,22 @@
|
||||
module de-graphql
|
||||
|
||||
go 1.19
|
||||
|
||||
replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230410091208-df60dd43079c
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.0.0-rc.1 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
29
plugins/wasm-go/extensions/de-graphql/go.sum
Normal file
29
plugins/wasm-go/extensions/de-graphql/go.sum
Normal file
@@ -0,0 +1,29 @@
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230410091208-df60dd43079c h1:W1QzLx6pefqDWi4peW2HKcZY0rgEy11+JCuWtssp1Ew=
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230410091208-df60dd43079c/go.mod h1:AzSnkuon5c26nIePTiJQIAFsKdhkNdncLcTuahpGtQs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
|
||||
github.com/tetratelabs/wazero v1.0.0-rc.1 h1:ytecMV5Ue0BwezjKh/cM5yv1Mo49ep2R2snSsQUyToc=
|
||||
github.com/tetratelabs/wazero v1.0.0-rc.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
59
plugins/wasm-go/extensions/de-graphql/graphql.yaml
Normal file
59
plugins/wasm-go/extensions/de-graphql/graphql.yaml
Normal file
@@ -0,0 +1,59 @@
|
||||
apiVersion: networking.higress.io/v1
|
||||
kind: McpBridge
|
||||
metadata:
|
||||
name: default
|
||||
namespace: higress-system
|
||||
spec:
|
||||
registries:
|
||||
- domain: api.github.com
|
||||
name: github
|
||||
port: 443
|
||||
type: dns
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/destination: github.dns
|
||||
higress.io/upstream-vhost: "api.github.com"
|
||||
higress.io/backend-protocol: HTTPS
|
||||
name: github-api
|
||||
namespace: higress-system
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- backend:
|
||||
resource:
|
||||
apiGroup: networking.higress.io
|
||||
kind: McpBridge
|
||||
name: default
|
||||
path: /api
|
||||
pathType: Prefix
|
||||
---
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: de-graphql-github-api
|
||||
namespace: higress-system
|
||||
spec:
|
||||
defaultConfigDisable: true
|
||||
matchRules:
|
||||
- config:
|
||||
domain: api.github.com
|
||||
endpoint: /graphql
|
||||
gql: |-
|
||||
query ($owner:String! $name:String!){
|
||||
repository(owner:$owner, name:$name) {
|
||||
name
|
||||
forkCount
|
||||
description
|
||||
}
|
||||
}
|
||||
timeout: 5000
|
||||
configDisable: false
|
||||
ingress:
|
||||
- github-api
|
||||
url: oci://docker.io/2456868764/de-graphql:1.0.0
|
||||
|
||||
117
plugins/wasm-go/extensions/de-graphql/main.go
Normal file
117
plugins/wasm-go/extensions/de-graphql/main.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"de-graphql/config"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wrapper.SetCtx(
|
||||
"de-graphql",
|
||||
wrapper.ParseConfigBy(parseConfig),
|
||||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
|
||||
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
|
||||
wrapper.ProcessResponseBodyBy(onHttpResponseBody),
|
||||
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
|
||||
)
|
||||
}
|
||||
|
||||
func parseConfig(json gjson.Result, config *config.DeGraphQLConfig, log wrapper.Log) error {
|
||||
log.Debug("parseConfig()")
|
||||
gql := json.Get("gql").String()
|
||||
endpoint := json.Get("endpoint").String()
|
||||
timeout := json.Get("timeout").Int()
|
||||
domain := json.Get("domain").String()
|
||||
log.Debugf("gql:%s endpoint:%s timeout:%d domain:%s", gql, endpoint, timeout, domain)
|
||||
err := config.SetGql(gql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = config.SetEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.SetTimeout(uint32(timeout))
|
||||
config.SetDomain(domain)
|
||||
return nil
|
||||
}
|
||||
|
||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, config config.DeGraphQLConfig, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpRequestHeaders()")
|
||||
log.Debugf("schema:%s host:%s path:%s", ctx.Scheme(), ctx.Host(), ctx.Path())
|
||||
requestUrl, _ := proxywasm.GetHttpRequestHeader(":path")
|
||||
method, _ := proxywasm.GetHttpRequestHeader(":method")
|
||||
log.Debugf("method:%s, request:%s", method, requestUrl)
|
||||
if err := proxywasm.RemoveHttpRequestHeader("content-length"); err != nil {
|
||||
log.Debug("can not reset content-length")
|
||||
}
|
||||
replaceBody, err := config.ParseGqlFromUrl(requestUrl)
|
||||
if err != nil {
|
||||
log.Warnf("failed to parse request url %s : %v", requestUrl, err)
|
||||
}
|
||||
log.Debugf("replace body:%s", replaceBody)
|
||||
|
||||
// Pass headers to upstream cluster
|
||||
headers, _ := proxywasm.GetHttpRequestHeaders()
|
||||
for i := len(headers) - 1; i >= 0; i-- {
|
||||
key := headers[i][0]
|
||||
if key == ":method" || key == ":path" || key == ":authority" {
|
||||
headers = append(headers[:i], headers[i+1:]...)
|
||||
}
|
||||
}
|
||||
// Add header Content-Type: application/json
|
||||
headers = append(headers, [2]string{"Content-Type", "application/json"})
|
||||
client := wrapper.NewClusterClient(wrapper.RouteCluster{Host: config.GetDomain()})
|
||||
// Call upstream graphql endpoint
|
||||
client.Post(config.GetEndpoint(), headers, []byte(replaceBody),
|
||||
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
// Pass response headers and body to client
|
||||
headers := make([][2]string, 0, len(responseHeaders)+3)
|
||||
for headK, headV := range responseHeaders {
|
||||
headers = append(headers, [2]string{headK, headV[0]})
|
||||
}
|
||||
// Add debug headers
|
||||
headers = append(headers, [2]string{"x-degraphql-endpoint", config.GetEndpoint()})
|
||||
headers = append(headers, [2]string{"x-degraphql-timeout", fmt.Sprintf("%d", config.GetTimeout())})
|
||||
headers = append(headers, [2]string{"x-degraphql-version", config.GetVersion()})
|
||||
proxywasm.SendHttpResponse(uint32(statusCode), headers, responseBody, -1)
|
||||
return
|
||||
}, config.GetTimeout())
|
||||
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
func onHttpRequestBody(ctx wrapper.HttpContext, config config.DeGraphQLConfig, body []byte, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpRequestBody()")
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpResponseHeaders(ctx wrapper.HttpContext, config config.DeGraphQLConfig, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpResponseHeaders()")
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpResponseBody(ctx wrapper.HttpContext, config config.DeGraphQLConfig, body []byte, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpResponseBody()")
|
||||
return types.ActionContinue
|
||||
}
|
||||
50
plugins/wasm-go/extensions/gw-error-format/README.md
Normal file
50
plugins/wasm-go/extensions/gw-error-format/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 功能说明
|
||||
`gw-error-format`本插件实现了匹配网关未转发到后端服务时的响应状态码和响应内容体并替换返回自定义响应内容
|
||||
|
||||
# 配置字段
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| rules.match.statuscode | string | 必填 | - | 匹配响应状态码 |
|
||||
| rules.match.responsebody | string | 必填 | - | 匹配响应体 |
|
||||
| rules.replace.statuscode | string | 必填 | - | 替换后的响应状态码 |
|
||||
| rules.replace.responsebody | string | 必填 | - | 替换后的响应体 |
|
||||
| set_header | array of object | 选填 | - | 添加/替换响应头,例如:- content-type: "application/json" |
|
||||
|
||||
# 配置示例
|
||||
```yaml
|
||||
rules:
|
||||
- match:
|
||||
statuscode: "403"
|
||||
responsebody: "RBAC: access denied"
|
||||
replace:
|
||||
statuscode: "200"
|
||||
responsebody: "{\"code\":401,\"message\":\"User is not authenticated\"}"
|
||||
- match:
|
||||
statuscode: "503"
|
||||
responsebody: "no healthy upstream"
|
||||
replace:
|
||||
statuscode: "200"
|
||||
responsebody: "{\"code\":404,\"message\":\"No Healthy Service\"}"
|
||||
set_header:
|
||||
- Access-Control-Allow-Credentials: "true"
|
||||
- Access-Control-Allow-Origin: "*"
|
||||
- Access-Control-Allow-Headers: "*"
|
||||
- Access-Control-Allow-Methods: "*"
|
||||
- Access-Control-Expose-Headers: "*"
|
||||
- Content-Type: "application/json;charset=UTF-8"
|
||||
```
|
||||
|
||||
## 示例说明:
|
||||
以上配置示例作用于当前实例全局生效
|
||||
|
||||
match下指定的statuscode和responsebody将被替换为同级中的replace下的statuscode和responsebody
|
||||
|
||||
以上示例当某个请求返回的响应状态码是403并且响应内容体是RBAC: access denied的则替换状态码为200和响应内容体为json格式"{"code":401,"message":"User is not authenticated"}"
|
||||
|
||||
如果需要新增/替换response header则可以在rules同级中添加set_header字段,当有match下的statuscode匹配上之后会将set_header的内容带在response header
|
||||
|
||||
|
||||
## 小提示:
|
||||
当envoy网关还未转发至后端服务时response header里面不会带有这个header:x-envoy-upstream-service-time
|
||||
本插件只在没有获取到此x-envoy-upstream-service-time响应头时生效
|
||||
|
||||
1
plugins/wasm-go/extensions/gw-error-format/VERSION
Normal file
1
plugins/wasm-go/extensions/gw-error-format/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
20
plugins/wasm-go/extensions/gw-error-format/go.mod
Normal file
20
plugins/wasm-go/extensions/gw-error-format/go.mod
Normal file
@@ -0,0 +1,20 @@
|
||||
module wasm-demo
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/mse-group/wasm-extensions-go v1.0.1
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20221116034346-4eb91e6918b8
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-redis/redis v6.15.9+incompatible // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
)
|
||||
24
plugins/wasm-go/extensions/gw-error-format/go.sum
Normal file
24
plugins/wasm-go/extensions/gw-error-format/go.sum
Normal file
@@ -0,0 +1,24 @@
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20221116034346-4eb91e6918b8 h1:mpxRyDnAED+3xv5Lx92jVJZyEm1lKlTpryNnGK/Ikz4=
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20221116034346-4eb91e6918b8/go.mod h1:JZEtmL2/oa24moc8fVXug1gMsOd/dnQM38e3pR5tZ/M=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/mse-group/wasm-extensions-go v1.0.0 h1:hYkU8sIs8/rTEThrG8kEl8woh3tklEWeljGJS11rJe0=
|
||||
github.com/mse-group/wasm-extensions-go v1.0.0/go.mod h1:N9MtZ4Oreog4gx67BBVJGM+cl/SgRy1Vm5OEKidQEYM=
|
||||
github.com/mse-group/wasm-extensions-go v1.0.1 h1:9AotUmzsc6R0X8uezQj3OHgId0YCNPCPubXT+8ciY0E=
|
||||
github.com/mse-group/wasm-extensions-go v1.0.1/go.mod h1:N9MtZ4Oreog4gx67BBVJGM+cl/SgRy1Vm5OEKidQEYM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c h1:OCUFXVGixHLfNjg6/QYEhv+jHJ5mRGhpEUVFv9eWPJE=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c/go.mod h1:5t/pWFNJ9eMyu/K/Z+OeGhDJ9sN9eCo8fc2pyM/Qjg4=
|
||||
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: extensions.istio.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: gw-error-format
|
||||
namespace: higress-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
higress: higress-system-higress-gateway
|
||||
pluginConfig:
|
||||
rules:
|
||||
- match:
|
||||
statuscode: "200"
|
||||
responsebody: "bar"
|
||||
replace:
|
||||
statuscode: "401"
|
||||
responsebody: "{\"code\":401,\"message\":\"User is not authenticated\"}"
|
||||
- match:
|
||||
statuscode: "503"
|
||||
responsebody: "no healthy upstream"
|
||||
replace:
|
||||
statuscode: "200"
|
||||
responsebody: "{\"code\":404,\"message\":\"No Healthy Service\"}"
|
||||
set_header:
|
||||
- access-control-allow-credentials: "true"
|
||||
- access-control-allow-origin: "*"
|
||||
- access-control-expose-headers: "*"
|
||||
- content-type: "application/json;charset=UTF-8"
|
||||
- custom-header: "HelloWorld"
|
||||
url: oci://docker.io/zhangjiahaol/envoy-plugin:gw-error-format-2.0.0
|
||||
98
plugins/wasm-go/extensions/gw-error-format/main.go
Normal file
98
plugins/wasm-go/extensions/gw-error-format/main.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wrapper.SetCtx(
|
||||
"gw-error-format",
|
||||
wrapper.ParseConfigBy(parseConfig),
|
||||
wrapper.ProcessResponseHeadersBy(onHttpResponseHeader),
|
||||
wrapper.ProcessResponseBodyBy(onHttpResponseBody),
|
||||
)
|
||||
}
|
||||
|
||||
type MyConfig struct {
|
||||
rules []gjson.Result
|
||||
set_header []gjson.Result
|
||||
}
|
||||
|
||||
func parseConfig(json gjson.Result, config *MyConfig, log wrapper.Log) error {
|
||||
config.set_header = json.Get("set_header").Array()
|
||||
config.rules = json.Get("rules").Array()
|
||||
for _, item := range config.rules {
|
||||
log.Info("config.rules: " + item.String())
|
||||
if item.Get("match.statuscode").String() == "" {
|
||||
return errors.New("missing match.statuscode in config")
|
||||
}
|
||||
if item.Get("replace.statuscode").String() == "" {
|
||||
return errors.New("missing replace.statuscode in config")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func onHttpResponseHeader(ctx wrapper.HttpContext, config MyConfig, log wrapper.Log) types.Action {
|
||||
dontReadResponseBody := false
|
||||
currentStatuscode, _ := proxywasm.GetHttpResponseHeader(":status")
|
||||
|
||||
for _, item := range config.rules {
|
||||
configMatchStatuscode := item.Get("match.statuscode").String()
|
||||
configReplaceStatuscode := item.Get("replace.statuscode").String()
|
||||
switch currentStatuscode {
|
||||
// configMatchStatuscode value example: "403" or "503":
|
||||
case configMatchStatuscode:
|
||||
// If the response header `x-envoy-upstream-service-time` is not found, the request has not been forwarded to the backend service
|
||||
_, err := proxywasm.GetHttpResponseHeader("x-envoy-upstream-service-time")
|
||||
if err != nil {
|
||||
proxywasm.RemoveHttpResponseHeader("content-length")
|
||||
proxywasm.ReplaceHttpResponseHeader(":status", configReplaceStatuscode)
|
||||
for _, item_header := range config.set_header {
|
||||
item_header.ForEach(func(key, value gjson.Result) bool {
|
||||
err := proxywasm.ReplaceHttpResponseHeader(key.String(), value.String())
|
||||
if err != nil {
|
||||
log.Critical("failed ReplaceHttpResponseHeader" + item_header.String())
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
// goto func onHttpResponseBody
|
||||
return types.ActionContinue
|
||||
} else {
|
||||
dontReadResponseBody = true
|
||||
break
|
||||
}
|
||||
default:
|
||||
// There is no matching rule
|
||||
dontReadResponseBody = true
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no rule match or no header for x-envoy-upstream-service-time, the onHttpResponseBody is not exec
|
||||
if dontReadResponseBody == true {
|
||||
ctx.DontReadResponseBody()
|
||||
}
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpResponseBody(ctx wrapper.HttpContext, config MyConfig, body []byte, log wrapper.Log) types.Action {
|
||||
bodyStr := string(body)
|
||||
|
||||
for _, item := range config.rules {
|
||||
configMatchResponsebody := item.Get("match.responsebody").String()
|
||||
configReplaceResponsebody := item.Get("replace.responsebody").String()
|
||||
if bodyStr == configMatchResponsebody {
|
||||
proxywasm.ReplaceHttpResponseBody([]byte(configReplaceResponsebody))
|
||||
return types.ActionContinue
|
||||
}
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
@@ -17,6 +17,8 @@ package wrapper
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
|
||||
)
|
||||
|
||||
type Cluster interface {
|
||||
@@ -24,6 +26,25 @@ type Cluster interface {
|
||||
HostName() string
|
||||
}
|
||||
|
||||
type RouteCluster struct {
|
||||
Host string
|
||||
}
|
||||
|
||||
func (c RouteCluster) ClusterName() string {
|
||||
routeName, err := proxywasm.GetProperty([]string{"cluster_name"})
|
||||
if err != nil {
|
||||
proxywasm.LogErrorf("get route cluster failed, err:%v", err)
|
||||
}
|
||||
return string(routeName)
|
||||
}
|
||||
|
||||
func (c RouteCluster) HostName() string {
|
||||
if c.Host != "" {
|
||||
return c.Host
|
||||
}
|
||||
return GetRequestHost()
|
||||
}
|
||||
|
||||
type K8sCluster struct {
|
||||
ServiceName string
|
||||
Namespace string
|
||||
|
||||
@@ -115,7 +115,7 @@ func HttpCall(cluster Cluster, method, path string, headers [][2]string, body []
|
||||
requestID, code, normalResponse, respBody)
|
||||
callback(code, headers, respBody)
|
||||
})
|
||||
proxywasm.LogDebugf("http call start, id: %s, cluster: %+v, method: %s, path: %s, body: %s, timeout: %d",
|
||||
requestID, cluster, method, path, body, timeout)
|
||||
proxywasm.LogDebugf("http call start, id: %s, cluster: %s, method: %s, path: %s, body: %s, timeout: %d",
|
||||
requestID, cluster.ClusterName(), method, path, body, timeout)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ type HttpContext interface {
|
||||
type ParseConfigFunc[PluginConfig any] func(json gjson.Result, config *PluginConfig, log Log) error
|
||||
type onHttpHeadersFunc[PluginConfig any] func(context HttpContext, config PluginConfig, log Log) types.Action
|
||||
type onHttpBodyFunc[PluginConfig any] func(context HttpContext, config PluginConfig, body []byte, log Log) types.Action
|
||||
type onHttpStreamDoneFunc[PluginConfig any] func(context HttpContext, config PluginConfig, log Log)
|
||||
|
||||
type CommonVmCtx[PluginConfig any] struct {
|
||||
types.DefaultVMContext
|
||||
@@ -51,6 +52,7 @@ type CommonVmCtx[PluginConfig any] struct {
|
||||
onHttpRequestBody onHttpBodyFunc[PluginConfig]
|
||||
onHttpResponseHeaders onHttpHeadersFunc[PluginConfig]
|
||||
onHttpResponseBody onHttpBodyFunc[PluginConfig]
|
||||
onHttpStreamDone onHttpStreamDoneFunc[PluginConfig]
|
||||
}
|
||||
|
||||
func SetCtx[PluginConfig any](pluginName string, setFuncs ...SetPluginFunc[PluginConfig]) {
|
||||
@@ -89,6 +91,12 @@ func ProcessResponseBodyBy[PluginConfig any](f onHttpBodyFunc[PluginConfig]) Set
|
||||
}
|
||||
}
|
||||
|
||||
func ProcessStreamDoneBy[PluginConfig any](f onHttpStreamDoneFunc[PluginConfig]) SetPluginFunc[PluginConfig] {
|
||||
return func(ctx *CommonVmCtx[PluginConfig]) {
|
||||
ctx.onHttpStreamDone = f
|
||||
}
|
||||
}
|
||||
|
||||
func parseEmptyPluginConfig[PluginConfig any](gjson.Result, *PluginConfig, Log) error {
|
||||
return nil
|
||||
}
|
||||
@@ -289,3 +297,13 @@ func (ctx *CommonHttpCtx[PluginConfig]) OnHttpResponseBody(bodySize int, endOfSt
|
||||
}
|
||||
return ctx.plugin.vm.onHttpResponseBody(ctx, *ctx.config, body, ctx.plugin.vm.log)
|
||||
}
|
||||
|
||||
func (ctx *CommonHttpCtx[PluginConfig]) OnHttpStreamDone() {
|
||||
if ctx.config == nil {
|
||||
return
|
||||
}
|
||||
if ctx.plugin.vm.onHttpStreamDone == nil {
|
||||
return
|
||||
}
|
||||
ctx.plugin.vm.onHttpStreamDone(ctx, *ctx.config, ctx.plugin.vm.log)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,13 @@ Higress e2e tests are mainly focusing on two parts for now:
|
||||
|
||||

|
||||
|
||||
Higress provides make target to run ingress api conformance tests: `make ingress-conformance-test`. It can be divided into below steps:
|
||||
Higress provides make target to run ingress api conformance tests and wasmplugin tests,
|
||||
|
||||
+ API Tests: `make ingress-conformance-test`
|
||||
+ WasmPlugin Tests: `make ingress-wasmplugin-test`
|
||||
+ Only build one WasmPlugin for testing: `PLUGIN_NAME=request-block make ingress-wasmplugin-test`
|
||||
|
||||
It can be divided into below steps:
|
||||
|
||||
1. delete-cluster: checks if we have undeleted kind cluster.
|
||||
2. create-cluster: create a new kind cluster.
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/cert"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/kubernetes"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteDownstreamEncryption)
|
||||
}
|
||||
|
||||
var HTTPRouteDownstreamEncryption = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteDownstreamEncryption",
|
||||
Description: "A single Ingress in the higress-conformance-infra namespace for downstream encryption.",
|
||||
Manifests: []string{"tests/httproute-downstream-encryption.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
// Prepare certificates and secrets for testcases
|
||||
caCertOut, _, caCert, caKey := cert.MustGenerateCaCert(t)
|
||||
svcCertOut, svcKeyOut := cert.MustGenerateCertWithCA(t, cert.ServerCertType, caCert, caKey, []string{"foo.com"})
|
||||
cliCertOut, cliKeyOut := cert.MustGenerateCertWithCA(t, cert.ClientCertType, caCert, caKey, nil)
|
||||
fooSecret := kubernetes.ConstructTLSSecret("higress-conformance-infra", "foo-secret", svcCertOut.Bytes(), svcKeyOut.Bytes())
|
||||
fooSecretCACert := kubernetes.ConstructCASecret("higress-conformance-infra", "foo-secret-cacert", caCertOut.Bytes())
|
||||
suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{fooSecret, fooSecretCACert}, suite.Cleanup)
|
||||
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 1: auth-tls-secret annotation",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo1",
|
||||
Host: "foo1.com",
|
||||
TLSConfig: &http.TLSConfig{
|
||||
SNI: "foo1.com",
|
||||
Certificates: http.Certificates{
|
||||
CACerts: [][]byte{caCertOut.Bytes()},
|
||||
ClientKeyPairs: []http.ClientKeyPair{{
|
||||
ClientCert: cliCertOut.Bytes(),
|
||||
ClientKey: cliKeyOut.Bytes()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo1",
|
||||
Host: "foo1.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 2: ssl-cipher annotation, ingress of one cipher suite",
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo2",
|
||||
Host: "foo2.com",
|
||||
TLSConfig: &http.TLSConfig{
|
||||
SNI: "foo2.com",
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
|
||||
Certificates: http.Certificates{
|
||||
CACerts: [][]byte{caCertOut.Bytes()},
|
||||
ClientKeyPairs: []http.ClientKeyPair{{
|
||||
ClientCert: cliCertOut.Bytes(),
|
||||
ClientKey: cliKeyOut.Bytes()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo2",
|
||||
Host: "foo2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 3: ssl-cipher annotation, ingress of multiple cipher suites",
|
||||
TargetBackend: "infra-backend-v3",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo3",
|
||||
Host: "foo3.com",
|
||||
TLSConfig: &http.TLSConfig{
|
||||
SNI: "foo3.com",
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
|
||||
Certificates: http.Certificates{
|
||||
CACerts: [][]byte{caCertOut.Bytes()},
|
||||
ClientKeyPairs: []http.ClientKeyPair{{
|
||||
ClientCert: cliCertOut.Bytes(),
|
||||
ClientKey: cliKeyOut.Bytes()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo3",
|
||||
Host: "foo3.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 4: ssl-cipher annotation, TLSv1.2 cipher suites are invalid in TLSv1.3",
|
||||
TargetBackend: "infra-backend-v3",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo3",
|
||||
Host: "foo3.com",
|
||||
TLSConfig: &http.TLSConfig{
|
||||
SNI: "foo3.com",
|
||||
MinVersion: tls.VersionTLS13,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
|
||||
Certificates: http.Certificates{
|
||||
CACerts: [][]byte{caCertOut.Bytes()},
|
||||
ClientKeyPairs: []http.ClientKeyPair{{
|
||||
ClientCert: cliCertOut.Bytes(),
|
||||
ClientKey: cliKeyOut.Bytes()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo3",
|
||||
Host: "foo3.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Downstream encryption", func(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/auth-tls-secret: foo-secret-cacert
|
||||
name: httproute-downstream-encryption-auth
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
tls:
|
||||
- hosts:
|
||||
- "foo1.com"
|
||||
secretName: foo-secret
|
||||
rules:
|
||||
- host: "foo1.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Exact
|
||||
path: "/foo1"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/ssl-cipher: ECDHE-RSA-AES128-SHA
|
||||
higress.io/auth-tls-secret: foo-secret-cacert
|
||||
name: httproute-downstream-encryption-cipher-1
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
tls:
|
||||
- hosts:
|
||||
- "foo2.com"
|
||||
secretName: foo-secret
|
||||
rules:
|
||||
- host: "foo2.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Exact
|
||||
path: "/foo2"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/ssl-cipher: ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES128-SHA,ECDHE-ECDSA-AES256-SHA
|
||||
higress.io/auth-tls-secret: foo-secret-cacert
|
||||
name: httproute-downstream-encryption-cipher-2
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
tls:
|
||||
- hosts:
|
||||
- "foo3.com"
|
||||
secretName: foo-secret
|
||||
rules:
|
||||
- host: "foo3.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Exact
|
||||
path: "/foo3"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v3
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/roundtripper"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HttpForceRedirectHttps)
|
||||
}
|
||||
|
||||
var HttpForceRedirectHttps = suite.ConformanceTest{
|
||||
ShortName: "HttpForceRedirectHttps",
|
||||
Description: " The ingress in the higress-conformance-infra namespace enforces server-side HTTPS with forced redirection.",
|
||||
Manifests: []string{"tests/httproute-force-redirect-https.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "test.com",
|
||||
Path: "/test",
|
||||
UnfollowRedirect: true,
|
||||
},
|
||||
RedirectRequest: &roundtripper.RedirectRequest{
|
||||
Scheme: "https",
|
||||
Host: "test.com",
|
||||
Path: "/test",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 308,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("HttpForceRedirectHttps", func(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
name: http-redirect-as-https
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
tls:
|
||||
- hosts:
|
||||
- "test.com"
|
||||
secretName: my-app-tls-secret
|
||||
rules:
|
||||
- host: "test.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/test"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: my-app-tls-secret
|
||||
namespace: higress-conformance-infra
|
||||
type: kubernetes.io/tls
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQxekNDQXIrZ0F3SUJBZ0lVWXh4dE1Ia0tIQXpxM25yUG0rd0Y2UEtNdmw4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2V6RUxNQWtHQTFVRUJoTUNRMDR4Q3pBSkJnTlZCQWdNQWxOWU1Rc3dDUVlEVlFRSERBSllRVEVOTUFzRwpBMVVFQ2d3RVdGVlFWREVUTUJFR0ExVUVDd3dLVEVsT1ZWaEhVazlWVURFTU1Bb0dBMVVFQXd3RFJsbFVNU0F3CkhnWUpLb1pJaHZjTkFRa0JGaEZtWjNrNE9UTTJRR2R0WVdsc0xtTnZiVEFlRncweU16QTFNRGd4TkRVM016UmEKRncweU5EQTFNRGN4TkRVM016UmFNSHN4Q3pBSkJnTlZCQVlUQWtOT01Rc3dDUVlEVlFRSURBSlRXREVMTUFrRwpBMVVFQnd3Q1dFRXhEVEFMQmdOVkJBb01CRmhWVUZReEV6QVJCZ05WQkFzTUNreEpUbFZZUjFKUFZWQXhEREFLCkJnTlZCQU1NQTBaWlZERWdNQjRHQ1NxR1NJYjNEUUVKQVJZUlptZDVPRGt6TmtCbmJXRnBiQzVqYjIwd2dnRWkKTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEUEVHaDJxeFpFMmpJUnMvNkFXYkI2b3oxZQpic0c4enE0YWxLTzVUcjgzWFlrUzhOa2g4UkdiU1l3ODlVR3NBWmZ0WkFqT0M2Mml1aEVOUTUzZjhwTmoySWQ1Ck9PNGVhVDN6bndKQ0xGMmRHcThRZE90c1RjU09FZE11N2dORWVOZkxVeWRFNitnYjcxSi9PRkNlZTlQM1dWWWgKQ05adG1nYWcyWm0wQUZxT0F2b1hUV3lGdDBzWEYyVG90VENnWFhNM1kydmdCY3JRMHRTbllHZmVqOVRUcmpENgpGQTBTYmFlL0F6Y001cC9FNmdKNWFXREhLekY5c2lvOHRUOUZuN1Fzb3djR1BSTElOL1o3OGxhaEZITGpsVFBtCmZqUEFmdWVUWVYzY05ZNXRGNjZlV1duazI0WG4vTEFaSlhHU0hXRm5aWHhxZWIxQlBQQlRKSFpWNmFScEFnTUIKQUFHalV6QlJNQjBHQTFVZERnUVdCQlFScHRWS3hCNGpGTjJnZTAwVStBd3FLM2czTkRBZkJnTlZIU01FR0RBVwpnQlFScHRWS3hCNGpGTjJnZTAwVStBd3FLM2czTkRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwR0NTcUdTSWIzCkRRRUJDd1VBQTRJQkFRQ2tJTVJKYzRqZWtCazZUTUVUckFmOWJiKzBMcVkyYi9EMUlXdjUycFZzRkNmeWRDQ0wKRS9KVU1USGpTUXZvd1FRSHh1S291d3VHd2VoVFVocHJISzNzUXptUnZLTkpMVGlkT0tlNWJSUEZuTEVCa1JMRQpnQ1hrRXFNY2dvSjlMdzQvWW5sVm5UakRxK1lVN21QUkJlV3U3WDNFTXE4MWpjNHg1RWtubDZXem95MjIxd1RKCkhMTEl2OGFsbTBuYzAzV2lBbVBsUGpLL3Z3N0lRNDlKMTlydnROMXNDQ2xyUDBSVyt1NjRQL0luL3pBeE1HMC8KeGkwTTdjYk1GYjh5UGFDeERPWVQ0enljdWRUWlhNS0FReDRxLzRhYVpRK1oxV2FBTkVtQi9OM1hNTHBTTUZJaApEdjdCbUVVOWRSUkx6dklQMHIxNDlKNnlaZ2VQYzc2WWR5R0oKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRFBFR2gycXhaRTJqSVIKcy82QVdiQjZvejFlYnNHOHpxNGFsS081VHI4M1hZa1M4TmtoOFJHYlNZdzg5VUdzQVpmdFpBak9DNjJpdWhFTgpRNTNmOHBOajJJZDVPTzRlYVQzem53SkNMRjJkR3E4UWRPdHNUY1NPRWRNdTdnTkVlTmZMVXlkRTYrZ2I3MUovCk9GQ2VlOVAzV1ZZaENOWnRtZ2FnMlptMEFGcU9Bdm9YVFd5RnQwc1hGMlRvdFRDZ1hYTTNZMnZnQmNyUTB0U24KWUdmZWo5VFRyakQ2RkEwU2JhZS9BemNNNXAvRTZnSjVhV0RIS3pGOXNpbzh0VDlGbjdRc293Y0dQUkxJTi9aNwo4bGFoRkhMamxUUG1malBBZnVlVFlWM2NOWTV0RjY2ZVdXbmsyNFhuL0xBWkpYR1NIV0ZuWlh4cWViMUJQUEJUCkpIWlY2YVJwQWdNQkFBRUNnZ0VBRSs5UzkxWEtXNCtjTVdzZ1RmQVVsd0gvUndlbnZFc3pwTmg1bUw0Vmw3bDQKR0d3Nm8xTm5yQWtkS01NOTh0Ym1ieExwN0JoZ3U2RnBRZHNvS0diY3ZNaWNabFhPU3Z3NzNDZ0xXaDZXVnFrNgpnSDJaS3NDajh6K1JFdHdVVVhQRzVzclhKWUlHd3lXN3pnYTRjRUdncXhnZFBDbnpKdk1rdnppajNSb0puZEZNCktMMjBjeDArUDROMnZLem1FSDJaYmZLUUo0bXlpTlUzdTFjWE14L1hhU04yczJNSExqNHVZemFJV0Q1clU0S1YKOHVrTmNnT3ZFSHl1eUFYcGgyYlVXdjVMcWIvWnFuQnVqVDI1ZFF6Zk43NC81a3grR1dNVkpVMXM1cUFqOEVyMApWZXhhK0FkMU9hM2JTMktEVGt4MHROQVZ1NGprT2tLSkZxVWJjc2RtendLQmdRRHl1T2diSW9CcVY2NjRLTVhlCk1lendkRGVLdTV3dkhUUEhDQStnQlRkbE5Kb1orS3g2Z2FVOXhsN0o2Q0pIcXB2Ti8xdGZFdVY1bzMySmhMdzEKQWtJMDY1ODQ3Slgyb1BvWDFYdU4xelJNUjk4bW05YWkvNk10d20vYWpoOVdKNnhKV2tCYUpyVXB1UGV5K2d5QQp6cDRhSXFCY2xXUjJWK0dkS3RHODROeWNKd0tCZ1FEYVpDUjVMVzBSMmc2bTNHUk9nQS9vY0RMTEM3V0ovVzliCkxUcTZLcndWYlVKUFUvRk1IbG1wb3NBOHY0dUp2MklnM0FwTXphL0JJd2FCUHMvUXArN1hrZVI1em5MbEg2RlEKK3VNQnVRRDhBRXQxZTZiVzJYQkpCcDZjemJXMmF1bjYvUEd3WmpCZkdYT0RQNXJVWHFQNkpiZ2pqMjdwL2RYMwpFVzUrVGlyRTd3S0JnRjdLSzRyOVRGMDdaUFp5cGVPQ1o5LzM0d0VCQjV1MnNkUFdxQk44TmdnR0pQQmpseWc0Cm5VbWt3THZsTmczNjZPSG9DY3oxV2p6SXhtd0FOR2dYTzdmakZNbHNTNXlIZldQMWNVMFJjRkVoK0ZuaG5rOEYKdXJwU0p0Q1psRTlYS3dkeWdaTXpicWllbmMxOXJZaFlLSkpZVjN3UXM2MHI0T1k2SkxLNHRpOGRBb0dCQUpjeQpyK0hKWm5MdWtpaEorNVF4cTFIVXBBWFpkSFUxcGl2czAzVGljMWN1VHJOWFBYN2lvRmNHbTZzelBlcy9PalBmCnc2M0sxYnlVZ0VObzlqM1NsbFJlNkZ6QVp1RmtsYTNZRk9RemJwQUpzRFNGU0V3RlBHMENqVHVvVy84UVpDL2wKZ1hzTU5MOFNndHZDWkhKVmw1ZHZGOTVleG41dncvd0s4SUczb25xM0FvR0FiYVV6UGZJWkRiTlJvM2tKeEZxawoxellzd3ZUUTdqU1lvMGlSbUVNSG9KTStvYWJPaFZjN0NZSjFoK1ZXelBmWXJCUFE5VEZjZEI3b1hueW9OTlZOClBjdGtUYXc1LyszNWdJWThHcmJsdzlqWmtmalFFVDJPNkFmMG5tQTd4a1F2djZkZkgzQTI0WlRyTExrY0pJTzYKZGVtNFpXbitiUWFRNnBvYThJdngvelU9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
|
||||
89
test/ingress/conformance/tests/httproute-full-path-regex.go
Normal file
89
test/ingress/conformance/tests/httproute-full-path-regex.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteFullPathRegex)
|
||||
}
|
||||
|
||||
var HTTPRouteFullPathRegex = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteFullPathRegex",
|
||||
Description: "test for 'higress.io/full-path-regex' annotation",
|
||||
Manifests: []string{"tests/httproute-full-path-regex.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testCases := []http.Assertion{
|
||||
{
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{Path: "/foo/1234"},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
}, {
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{Path: "/bar/123"},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
}, {
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{Path: "/bar/1234"},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 404,
|
||||
},
|
||||
},
|
||||
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Test for 'higress.io/full-path-regex'", func(t *testing.T) {
|
||||
for _, testCase := range testCases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testCase)
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||
name: httproute-ingress-use-regex
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- pathType: ImplementationSpecific
|
||||
path: "/foo/[A-Z0-9]{3}"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/full-path-regex: "true"
|
||||
name: httproute-higress-full-path-regex
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- pathType: ImplementationSpecific
|
||||
path: "/bar/[A-Z0-9]{3}"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,50 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
name: http-redirect-as-https
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
tls:
|
||||
- hosts:
|
||||
- "test.com"
|
||||
secretName: my-app-tls-secret
|
||||
rules:
|
||||
- host: "test.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/test"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: my-app-tls-secret
|
||||
namespace: higress-conformance-infra
|
||||
type: kubernetes.io/tls
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURzVENDQXBtZ0F3SUJBZ0lVUGRDZENSdm1pbVZwK2VCcTMxUm9oZ1JsYTBzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2FERUxNQWtHQTFVRUJoTUNZWE14RERBS0JnTlZCQWdNQTJSaGN6RU1NQW9HQTFVRUJ3d0RZWE5rTVF3dwpDZ1lEVlFRS0RBTmhaSE14RERBS0JnTlZCQXNNQTNwNFl6RU5NQXNHQTFVRUF3d0VZWGRsY2pFU01CQUdDU3FHClNJYjNEUUVKQVJZRGNYZGxNQjRYRFRJek1EVXlNREEzTVRRd09Wb1hEVEkwTURVeE9UQTNNVFF3T1Zvd2FERUwKTUFrR0ExVUVCaE1DWVhNeEREQUtCZ05WQkFnTUEyUmhjekVNTUFvR0ExVUVCd3dEWVhOa01Rd3dDZ1lEVlFRSwpEQU5oWkhNeEREQUtCZ05WQkFzTUEzcDRZekVOTUFzR0ExVUVBd3dFWVhkbGNqRVNNQkFHQ1NxR1NJYjNEUUVKCkFSWURjWGRsTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUExeHB6Z21UVzdwQmUKa3ZlRkRBU0JZLzdpZmxvWFk1T0x3OUZwOXJqZHNMRmh5Y1dPR0U5NHJ1RmhDSkVqYmNzQW5URHZOKzg4WVRzOApkV2tWbVArQ1RQQjVoN2EvZmJtelJPYVE5SjVDQXhqaGJuRGhCc3YrYVpzeVlhVzBHQkp3Y3h6U1RoVUJpbm9aCmJwYUhvU1QxUjBlZ0FHQ2lkWk4wU2xDMW9KYmxOMHJoOGxKNUROcWxWSnBYejUxaGdLU1NmSm1UcElRQkhBQVkKSDVMUU1CTCtQem9INy83cWhUSUVaWWJvU0o2ajV1ZDc1YUZzVVhndmdLWFgvZ2JZTHlaQ0ljTXUyR2YzRjQ5bwoyc1QwdFZzQmxHbWsrVkswcmgxSi9xQjBWNDBheU8xR3NFalRhelBLUitrYTZJS1N6SjJLVkJadCtHQTdGMUkrCmlHOXcrVDQwVFFJREFRQUJvMU13VVRBZEJnTlZIUTRFRmdRVTFLZSs3aHZTaTljRjhXM2hZTW5hd0NKblcwVXcKSHdZRFZSMGpCQmd3Rm9BVTFLZSs3aHZTaTljRjhXM2hZTW5hd0NKblcwVXdEd1lEVlIwVEFRSC9CQVV3QXdFQgovekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBQ3JGQU1KV3g1QThsaXVLY1NETmlWY0Rvem5sU1p6cFYyeGRNCnpkSU5SRytFVXFzV3lpekNtWk5rSUMyV0lKTWhNdVBkOWhJdTZ3cWd5YjJaNnN3MmdjOXpwVnpsbkRKYlUwSlUKZEZJZDFuYmtQRG01ekk3NzAyRTk0eVZqUUs5ZllRVDU3cHFTdlkvOUpSeXRpcGdpdnAxMGR2UC95NUpocDVBawo3VHNQcnZ2K3gzWGhLYTRwRG40eEhzZHp4MGx0ZHdUdExiaWFGU1IwUHpBZzdpOUNsbjQ5aWRRd3YxaHpaNTV5CkFtOStESHhmTkgreGFIYk9zWTJFRHpiZ0FxR0JMaTNEMmtxMkdRREFmRDdOdGovRUNlODl1bG5zSWpMajB5YmUKNWR0QzRXYndBNER3UHBFWUJvbTFTZmdxQlJSSG5seTVIMUxZMS80ZnpUZkNIYkFtdWc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRRFhHbk9DWk5idWtGNlMKOTRVTUJJRmovdUorV2hkams0dkQwV24ydU4yd3NXSEp4WTRZVDNpdTRXRUlrU050eXdDZE1PODM3enhoT3p4MQphUldZLzRKTThIbUh0cjk5dWJORTVwRDBua0lER09GdWNPRUd5LzVwbXpKaHBiUVlFbkJ6SE5KT0ZRR0tlaGx1CmxvZWhKUFZIUjZBQVlLSjFrM1JLVUxXZ2x1VTNTdUh5VW5rTTJxVlVtbGZQbldHQXBKSjhtWk9raEFFY0FCZ2YKa3RBd0V2NC9PZ2Z2L3VxRk1nUmxodWhJbnFQbTUzdmxvV3hSZUMrQXBkZitCdGd2SmtJaHd5N1laL2NYajJqYQp4UFMxV3dHVWFhVDVVclN1SFVuK29IUlhqUnJJN1Vhd1NOTnJNOHBINlJyb2dwTE1uWXBVRm0zNFlEc1hVajZJCmIzRDVQalJOQWdNQkFBRUNnZ0VBTVRZT2ZpTDIzejV0UEo5Zk0zZ21hQXVzb3E2VzBrT3p3cGw2N2lTdUoxbjEKbnRWUkpIT3VEd2htRERFMFUwNlJ0ZVMzbmVyZ08vaHk1UU9sR3NzOThyOURkbzZUTWI3VjZpbjd1Tk1xRkE1UgpxTlF2VDBCRlZNRGFYbWVzRTZQSVVUV2pVWlRSdE80cE9sazY3MTJHdGdlSGJmNnR2RXQvVys4cUZuTGZQdTQ1Cm50Nld6NzgrRkVuUW5qWVk3R1N4bTlaVFEraUw3VGMyR09MMUFjdkxVcmV1VS9aMXNwTVFPTFRUUzBLbkJka0YKY0FVeFBkNGpnZ3lwbGNpSHBQaktNNG1VRGkvSDluTWFjNyt4K08zZFZCNXlpb2xLREJCOHUybDIvb3BPSzVVZwpTSUg0ajVqU1NJY3lNNUNSdkFpR2Q3eE9HaE1Zb3hQV1FNNnduNGMyMFFLQmdRRFpEbkc5TmNTeHVSbnNUSVFMCnZIMForUWFMZmZ4WGF6eWswM21NMldiOWd3NEoxOGF5ZlF4STk2RU13VXd5ODJkRFh5QldzWFgxK0NxMnd2ZUUKMnB1cU1kV3pvUWI4UnZlOVJ6RDlwM0Z1V2kyR3BmL3JsWkVmZ1lRek1FWENtWnN0VlVDZXo5aVZVdW5ZQzRoZQovNU83Zm12VkVlQ1k3TUFIT2JtWUNOWmNtUUtCZ1FEOXNreTdqNjViai9ZYzlER2pQVzNraUZKeWl5YU9rb2dQCnVtbk5vQ2w4bHhIZ2d1Ym8wT3pubEFZQ0M5V3pxTmdBa3E4eWNlMkdTVkFEOTNoZisycDNON3lGRktMS0I3L0YKTlFsUHMyV2pyK25DUytxSHBSdnpQUjgzN01DUkZnbVlxRlNlaXE5NlcyeStwVTJBYVJkQTlqYWtPeWd0TGl5YQpSbHFDeWNVUjFRS0JnUUROWXBLVFpGWmJpUGdUbFk5NC80RXMyMnVyQUtxUEdhVEhubWVzdEdaMHlkYTF6NXh2CmRrM3ltWWFsNkI0dk5BeHBQcEQrRjJ1ME5JQk9jWXYvQlZBNHFuRTVTTXl3V0lMQmNxVFR6K1pRY2pvVDUrMlMKd1BNU2FkNXJCV2x0S3lZZnJrUzRRWm9DS2ZPbC83dXBrSkw4M2pJdzZucW9tWlZYQVBNeC9tTEFPUUtCZ1FDQQpWeXplVGNlRTVvVTVESWYzN3VHakZSdXdlcGljMDZBbFpNYVZrWXFyVHJscWZJNVlCU2x6MWJ4Y1dLUlphUGN0CkF3ZkNXMFF3QlBLSHJ5K2tUc29EV1p6ekxnZFVjU3NnbHI0SkpkWXJRcGpkQkE2M1pGMkpaY2hmUUZRQ2tjVjEKQnVNWCtVemdkMVBCOWxvSXRpRmZhYThtMGc1M0hMN1BwUHV3NG1YaHFRS0JnQmY5NGRrTXplUW44SDBrK2xXcQpJV3lJaGtqOHpNUmw2ODNzRFFOQUdSYngyTnR2RjYyL2dUK1ZJSXdmSzV5MmI4WXEwNFVEQy9rL1hkK0lBc3dVClFTRGdUVFpmYzZkUkVtRU8zc0M0a0xYa1N3Y1BQZmcvTC92T29YRDJiZWxmWUFtaHhSUnByQ0p4ZVowRVBvRUEKc25RblpMN1VsZCtSTmF3dmJNR05XTXJiCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/roundtripper"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HttpRedirectAsHttps)
|
||||
}
|
||||
|
||||
var HttpRedirectAsHttps = suite.ConformanceTest{
|
||||
ShortName: "HttpRedirectAsHttps",
|
||||
Description: "The Ingress in the higress-conformance-infra namespace Server-side HTTPS enforcement through redirect.",
|
||||
Manifests: []string{"tests/httproute-redirct-as-https.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "test.com",
|
||||
Path: "/test",
|
||||
UnfollowRedirect: true,
|
||||
},
|
||||
RedirectRequest: &roundtripper.RedirectRequest{
|
||||
Scheme: "https",
|
||||
Host: "test.com",
|
||||
Path: "/test",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 308,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("HttpRedirectAsHttps", func(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
}
|
||||
@@ -32,6 +32,12 @@ var HTTPRouteRequestHeaderControl = suite.ConformanceTest{
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 1: add one",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo1",
|
||||
@@ -39,6 +45,8 @@ var HTTPRouteRequestHeaderControl = suite.ConformanceTest{
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo1",
|
||||
Host: "foo.com",
|
||||
Headers: map[string]string{
|
||||
"stage": "test",
|
||||
},
|
||||
@@ -53,6 +61,12 @@ var HTTPRouteRequestHeaderControl = suite.ConformanceTest{
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 2: add more",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo2",
|
||||
@@ -60,6 +74,8 @@ var HTTPRouteRequestHeaderControl = suite.ConformanceTest{
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo2",
|
||||
Host: "foo.com",
|
||||
Headers: map[string]string{
|
||||
"stage": "test",
|
||||
"canary": "true",
|
||||
@@ -75,6 +91,12 @@ var HTTPRouteRequestHeaderControl = suite.ConformanceTest{
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 3: update one",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo3",
|
||||
@@ -85,9 +107,10 @@ var HTTPRouteRequestHeaderControl = suite.ConformanceTest{
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo3",
|
||||
Host: "foo.com",
|
||||
Headers: map[string]string{
|
||||
"stage": "pro",
|
||||
"canary": "true",
|
||||
"stage": "pro",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -100,19 +123,94 @@ var HTTPRouteRequestHeaderControl = suite.ConformanceTest{
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 4: update more",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo4",
|
||||
Host: "foo.com",
|
||||
Headers: map[string]string{
|
||||
"stage": "test",
|
||||
"canary": "true",
|
||||
},
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo4",
|
||||
Host: "foo.com",
|
||||
Headers: map[string]string{
|
||||
"stage": "pro",
|
||||
"canary": "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 5: remove one",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo5",
|
||||
Host: "foo.com",
|
||||
Headers: map[string]string{
|
||||
"stage": "test",
|
||||
},
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo5",
|
||||
Host: "foo.com",
|
||||
},
|
||||
AbsentHeaders: []string{"stage"},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case 6: remove more",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo6",
|
||||
Host: "foo.com",
|
||||
Headers: map[string]string{
|
||||
"stage": "test",
|
||||
"canary": "true",
|
||||
},
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Path: "/foo6",
|
||||
Host: "foo.com",
|
||||
},
|
||||
AbsentHeaders: []string{"stage", "canary"},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
|
||||
@@ -61,7 +61,7 @@ kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/request-header-control-update: stage pro
|
||||
name: httproute-request-header-control-update
|
||||
name: httproute-request-header-control-update-one
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
@@ -81,8 +81,10 @@ apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/request-header-control-remove: stage
|
||||
name: httproute-request-header-control-remove
|
||||
higress.io/request-header-control-update: |
|
||||
stage pro
|
||||
canary false
|
||||
name: httproute-request-header-control-update-more
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
@@ -97,3 +99,45 @@ spec:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/request-header-control-remove: stage
|
||||
name: httproute-request-header-control-remove-one
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: "foo.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Exact
|
||||
path: "/foo5"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/request-header-control-remove: stage,canary
|
||||
name: httproute-request-header-control-remove-more
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: "foo.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Exact
|
||||
path: "/foo6"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
@@ -30,9 +30,8 @@ var HTTPRouteRewritePath = suite.ConformanceTest{
|
||||
Description: "A single Ingress in the higress-conformance-infra namespace uses the rewrite path.",
|
||||
Manifests: []string{"tests/httproute-rewrite-path.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
|
||||
t.Run("Rewrite HTTPRoute Path", func(t *testing.T) {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.Assertion{
|
||||
testCases := []http.Assertion{
|
||||
{
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{Path: "/svc/foo"},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
@@ -50,7 +49,14 @@ var HTTPRouteRewritePath = suite.ConformanceTest{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Rewrite HTTPRoute Path", func(t *testing.T) {
|
||||
for _, testCase := range testCases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testCase)
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -31,3 +31,4 @@ spec:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
|
||||
59
test/ingress/conformance/tests/request-block.go
Normal file
59
test/ingress/conformance/tests/request-block.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, WasmPluginsRequestBlock)
|
||||
}
|
||||
|
||||
var WasmPluginsRequestBlock = suite.ConformanceTest{
|
||||
ShortName: "WasmPluginsRequestBlock",
|
||||
Description: "The Ingress in the higress-conformance-infra namespace test the request-block wasmplugins.",
|
||||
Manifests: []string{"tests/request-block.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/swagger.html",
|
||||
UnfollowRedirect: true,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("WasmPlugins request-block", func(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
45
test/ingress/conformance/tests/request-block.yaml
Normal file
45
test/ingress/conformance/tests/request-block.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/app-root: "/foo"
|
||||
name: httproute-app-root
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: "foo.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: request-block
|
||||
namespace: higress-system
|
||||
spec:
|
||||
defaultConfig:
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
url: file:///opt/plugins/wasm-go/extensions/request-block/plugin.wasm
|
||||
145
test/ingress/conformance/utils/cert/cert.go
Normal file
145
test/ingress/conformance/utils/cert/cert.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type CertType int
|
||||
|
||||
const (
|
||||
CACertType CertType = iota
|
||||
ServerCertType
|
||||
ClientCertType
|
||||
)
|
||||
|
||||
const (
|
||||
// RSABits defines the bit length of the RSA private key
|
||||
RSABits = 2048
|
||||
// ValidFor defines the certificate validity period
|
||||
ValidFor = 365 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// MustGenerateCaCert must generate a CA certificate and private key.
|
||||
// `certOut` and `keyOut` are PEM format buffers for certificate and private key, respectively.
|
||||
// `caCert` and `caKey` are the corresponding structures.
|
||||
func MustGenerateCaCert(t *testing.T) (certOut, keyOut *bytes.Buffer, caCert *x509.Certificate, caKey *rsa.PrivateKey) {
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(ValidFor)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1+int64(CACertType)), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
require.NoError(t, err, "failed to generate serial number")
|
||||
|
||||
caCert = &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "default",
|
||||
Organization: []string{"Higress E2E Test"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
caKey, err = rsa.GenerateKey(rand.Reader, RSABits)
|
||||
certOut, keyOut, err = GenerateCert(caCert, caKey, caCert, caKey)
|
||||
return
|
||||
}
|
||||
|
||||
// MustGenerateCertWithCA must generate a self-signed client/server certificate and private key
|
||||
// using CA certificate and private key.
|
||||
// `hosts` is used when CertType == ServerCertType
|
||||
func MustGenerateCertWithCA(t *testing.T, certType CertType, caCert *x509.Certificate, caKey *rsa.PrivateKey, hosts []string) (certOut, keyOut *bytes.Buffer) {
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(ValidFor)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1+int64(certType)), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
require.NoError(t, err, "failed to generate serial number")
|
||||
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "default",
|
||||
Organization: []string{"Higress E2E Test"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
if certType == ServerCertType && hosts != nil {
|
||||
for _, h := range hosts {
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, RSABits)
|
||||
require.NoError(t, err, "failed to generate ras key")
|
||||
certOut, keyOut, err = GenerateCert(template, privateKey, caCert, caKey)
|
||||
return
|
||||
}
|
||||
|
||||
// GenerateCert obtains the corresponding certificate and private key buffers
|
||||
// using the certificate template and private key.
|
||||
func GenerateCert(cert *x509.Certificate, key *rsa.PrivateKey, caCert *x509.Certificate, caKey *rsa.PrivateKey) (
|
||||
certOut, keyOut *bytes.Buffer, err error) {
|
||||
var (
|
||||
priv = key
|
||||
pub = &priv.PublicKey
|
||||
privPm = priv
|
||||
)
|
||||
if caKey != nil {
|
||||
privPm = caKey
|
||||
}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, cert, caCert, pub, privPm)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to create certificate: %w", err)
|
||||
return
|
||||
}
|
||||
certOut = new(bytes.Buffer)
|
||||
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed creating cert: %w", err)
|
||||
return
|
||||
}
|
||||
keyOut = new(bytes.Buffer)
|
||||
privDER := x509.MarshalPKCS1PrivateKey(priv)
|
||||
err = pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: privDER})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed creating key: %w", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -68,6 +68,10 @@ type TimeoutConfig struct {
|
||||
// RequestTimeout represents the maximum time for making an HTTP Request with the roundtripper.
|
||||
// Max value for conformant implementation: None
|
||||
RequestTimeout time.Duration
|
||||
|
||||
// TLSHandshakeTimeout represents the maximum time for waiting for a TLS handshake. Zero means no timeout.
|
||||
// Max value for conformant implementation: None
|
||||
TLSHandshakeTimeout time.Duration
|
||||
}
|
||||
|
||||
// DefaultTimeoutConfig populates a TimeoutConfig with the default values.
|
||||
@@ -86,6 +90,7 @@ func DefaultTimeoutConfig() TimeoutConfig {
|
||||
MaxTimeToConsistency: 30 * time.Second,
|
||||
NamespacesMustBeReady: 300 * time.Second,
|
||||
RequestTimeout: 10 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,4 +135,7 @@ func SetupTimeoutConfig(timeoutConfig *TimeoutConfig) {
|
||||
if timeoutConfig.RequestTimeout == 0 {
|
||||
timeoutConfig.RequestTimeout = defaultTimeoutConfig.RequestTimeout
|
||||
}
|
||||
if timeoutConfig.TLSHandshakeTimeout == 0 {
|
||||
timeoutConfig.TLSHandshakeTimeout = defaultTimeoutConfig.TLSHandshakeTimeout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,38 @@ type Request struct {
|
||||
Path string
|
||||
Headers map[string]string
|
||||
UnfollowRedirect bool
|
||||
TLSConfig *TLSConfig
|
||||
}
|
||||
|
||||
// TLSConfig defines the TLS configuration for the client.
|
||||
// When this field is set, the HTTPS protocol is used.
|
||||
type TLSConfig struct {
|
||||
// MinVersion specifies the minimum TLS version,
|
||||
// e.g. tls.VersionTLS12.
|
||||
MinVersion uint16
|
||||
// MinVersion specifies the maximum TLS version,
|
||||
// e.g. tls.VersionTLS13.
|
||||
MaxVersion uint16
|
||||
// SNI is short for Server Name Indication.
|
||||
// If this field is not specified, the value will be equal to `Host`.
|
||||
SNI string
|
||||
// CipherSuites can specify multiple client cipher suites,
|
||||
// e.g. tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA.
|
||||
CipherSuites []uint16
|
||||
// Certificates defines the certificate chain
|
||||
Certificates Certificates
|
||||
}
|
||||
|
||||
// Certificates contains CA and client certificate chain
|
||||
type Certificates struct {
|
||||
CACerts [][]byte
|
||||
ClientKeyPairs []ClientKeyPair
|
||||
}
|
||||
|
||||
// ClientKeyPair is a pair of client certificate and private key.
|
||||
type ClientKeyPair struct {
|
||||
ClientCert []byte
|
||||
ClientKey []byte
|
||||
}
|
||||
|
||||
// ExpectedRequest defines expected properties of a request that reaches a backend.
|
||||
@@ -102,6 +134,36 @@ const requiredConsecutiveSuccesses = 3
|
||||
func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripper.RoundTripper, timeoutConfig config.TimeoutConfig, gwAddr string, expected Assertion) {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
scheme = "http"
|
||||
protocol = "HTTP"
|
||||
tlsConfig *roundtripper.TLSConfig
|
||||
)
|
||||
if expected.Request.ActualRequest.TLSConfig != nil {
|
||||
scheme = "https"
|
||||
protocol = "HTTPS"
|
||||
clientKeyPairs := make([]roundtripper.ClientKeyPair, 0, len(expected.Request.ActualRequest.TLSConfig.Certificates.ClientKeyPairs))
|
||||
for _, keyPair := range expected.Request.ActualRequest.TLSConfig.Certificates.ClientKeyPairs {
|
||||
clientKeyPairs = append(clientKeyPairs, roundtripper.ClientKeyPair{
|
||||
ClientCert: keyPair.ClientCert,
|
||||
ClientKey: keyPair.ClientKey,
|
||||
})
|
||||
}
|
||||
tlsConfig = &roundtripper.TLSConfig{
|
||||
MinVersion: expected.Request.ActualRequest.TLSConfig.MinVersion,
|
||||
MaxVersion: expected.Request.ActualRequest.TLSConfig.MaxVersion,
|
||||
SNI: expected.Request.ActualRequest.TLSConfig.SNI,
|
||||
CipherSuites: expected.Request.ActualRequest.TLSConfig.CipherSuites,
|
||||
Certificates: roundtripper.Certificates{
|
||||
CACert: expected.Request.ActualRequest.TLSConfig.Certificates.CACerts,
|
||||
ClientKeyPairs: clientKeyPairs,
|
||||
},
|
||||
}
|
||||
if tlsConfig.SNI == "" {
|
||||
tlsConfig.SNI = expected.Request.ActualRequest.Host
|
||||
}
|
||||
}
|
||||
|
||||
if expected.Request.ActualRequest.Method == "" {
|
||||
expected.Request.ActualRequest.Method = "GET"
|
||||
}
|
||||
@@ -110,17 +172,18 @@ func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripp
|
||||
expected.Response.ExpectedResponse.StatusCode = 200
|
||||
}
|
||||
|
||||
t.Logf("Making %s request to http://%s%s", expected.Request.ActualRequest.Method, gwAddr, expected.Request.ActualRequest.Path)
|
||||
t.Logf("Making %s request to %s://%s%s", expected.Request.ActualRequest.Method, scheme, gwAddr, expected.Request.ActualRequest.Path)
|
||||
|
||||
path, query, _ := strings.Cut(expected.Request.ActualRequest.Path, "?")
|
||||
|
||||
req := roundtripper.Request{
|
||||
Method: expected.Request.ActualRequest.Method,
|
||||
Host: expected.Request.ActualRequest.Host,
|
||||
URL: url.URL{Scheme: "http", Host: gwAddr, Path: path, RawQuery: query},
|
||||
Protocol: "HTTP",
|
||||
URL: url.URL{Scheme: scheme, Host: gwAddr, Path: path, RawQuery: query},
|
||||
Protocol: protocol,
|
||||
Headers: map[string][]string{},
|
||||
UnfollowRedirect: expected.Request.ActualRequest.UnfollowRedirect,
|
||||
TLSConfig: tlsConfig,
|
||||
}
|
||||
|
||||
if expected.Request.ActualRequest.Headers != nil {
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
@@ -35,11 +34,8 @@ import (
|
||||
|
||||
// ensure auth plugins are loaded
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
const (
|
||||
rsaBits = 2048
|
||||
validFor = 365 * 24 * time.Hour
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/cert"
|
||||
)
|
||||
|
||||
// MustCreateSelfSignedCertSecret creates a self-signed SSL certificate and stores it in a secret
|
||||
@@ -47,49 +43,56 @@ func MustCreateSelfSignedCertSecret(t *testing.T, namespace, secretName string,
|
||||
require.Greater(t, len(hosts), 0, "require a non-empty hosts for Subject Alternate Name values")
|
||||
|
||||
var serverKey, serverCert bytes.Buffer
|
||||
|
||||
host := strings.Join(hosts, ",")
|
||||
|
||||
require.NoError(t, generateRSACert(host, &serverKey, &serverCert), "failed to generate RSA certificate")
|
||||
|
||||
data := map[string][]byte{
|
||||
corev1.TLSCertKey: serverCert.Bytes(),
|
||||
corev1.TLSPrivateKeyKey: serverKey.Bytes(),
|
||||
}
|
||||
return ConstructTLSSecret(namespace, secretName, serverCert.Bytes(), serverKey.Bytes())
|
||||
}
|
||||
|
||||
newSecret := &corev1.Secret{
|
||||
// ConstructTLSSecret constructs a secret of type "kubernetes.io/tls"
|
||||
func ConstructTLSSecret(namespace, secretName string, cert, key []byte) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: secretName,
|
||||
},
|
||||
Type: corev1.SecretTypeTLS,
|
||||
Data: data,
|
||||
Data: map[string][]byte{
|
||||
corev1.TLSCertKey: cert,
|
||||
corev1.TLSPrivateKeyKey: key,
|
||||
},
|
||||
}
|
||||
|
||||
return newSecret
|
||||
}
|
||||
|
||||
// generateRSACert generates a basic self signed certificate valir for a year
|
||||
func generateRSACert(host string, keyOut, certOut io.Writer) error {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate key: %w", err)
|
||||
// ConstructCASecret construct a CA secret of type "Opaque"
|
||||
func ConstructCASecret(namespace, secretName string, cert []byte) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: secretName,
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
corev1.ServiceAccountRootCAKey: cert,
|
||||
},
|
||||
}
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(validFor)
|
||||
}
|
||||
|
||||
// generateRSACert generates a basic self signed certificate valid for a year
|
||||
func generateRSACert(host string, keyOut, certOut io.Writer) error {
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(cert.ValidFor)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate serial number: %w", err)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "default",
|
||||
Organization: []string{"Acme Co"},
|
||||
Organization: []string{"Higress E2E Test"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
@@ -108,18 +111,13 @@ func generateRSACert(host string, keyOut, certOut io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, cert.RSABits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create certificate: %w", err)
|
||||
return fmt.Errorf("failed to generate key: %w", err)
|
||||
}
|
||||
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||
return fmt.Errorf("failed creating cert: %w", err)
|
||||
}
|
||||
|
||||
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
|
||||
return fmt.Errorf("failed creating key: %w", err)
|
||||
certOut, keyOut, err = cert.GenerateCert(template, priv, template, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate rsa certificate: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -16,6 +16,8 @@ package roundtripper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -41,6 +43,29 @@ type Request struct {
|
||||
Method string
|
||||
Headers map[string][]string
|
||||
UnfollowRedirect bool
|
||||
TLSConfig *TLSConfig
|
||||
}
|
||||
|
||||
// TLSConfig defines the TLS configuration for the client.
|
||||
// When this field is set, the HTTPS protocol is used.
|
||||
type TLSConfig struct {
|
||||
MinVersion uint16
|
||||
MaxVersion uint16
|
||||
SNI string
|
||||
CipherSuites []uint16
|
||||
Certificates Certificates
|
||||
}
|
||||
|
||||
// Certificates defines the self-signed client and CA certificate chain
|
||||
type Certificates struct {
|
||||
CACert [][]byte
|
||||
ClientKeyPairs []ClientKeyPair
|
||||
}
|
||||
|
||||
// ClientKeyPair is a pair of client certificate and private key.
|
||||
type ClientKeyPair struct {
|
||||
ClientCert []byte
|
||||
ClientKey []byte
|
||||
}
|
||||
|
||||
// CapturedRequest contains request metadata captured from an echoserver
|
||||
@@ -95,6 +120,35 @@ func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedReques
|
||||
}
|
||||
}
|
||||
|
||||
if request.TLSConfig != nil {
|
||||
pool := x509.NewCertPool()
|
||||
for _, caCert := range request.TLSConfig.Certificates.CACert {
|
||||
pool.AppendCertsFromPEM(caCert)
|
||||
}
|
||||
var clientCerts []tls.Certificate
|
||||
for _, keyPair := range request.TLSConfig.Certificates.ClientKeyPairs {
|
||||
newClientCert, err := tls.X509KeyPair(keyPair.ClientCert, keyPair.ClientKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load client key pair: %w", err)
|
||||
}
|
||||
clientCerts = append(clientCerts, newClientCert)
|
||||
}
|
||||
|
||||
client.Transport = &http.Transport{
|
||||
TLSHandshakeTimeout: d.TimeoutConfig.TLSHandshakeTimeout,
|
||||
DisableKeepAlives: true,
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: request.TLSConfig.MinVersion,
|
||||
MaxVersion: request.TLSConfig.MaxVersion,
|
||||
ServerName: request.TLSConfig.SNI,
|
||||
CipherSuites: request.TLSConfig.CipherSuites,
|
||||
RootCAs: pool,
|
||||
Certificates: clientCerts,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
method := "GET"
|
||||
if request.Method != "" {
|
||||
method = request.Method
|
||||
@@ -130,6 +184,7 @@ func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedReques
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer client.CloseIdleConnections()
|
||||
defer resp.Body.Close()
|
||||
|
||||
if d.Debug {
|
||||
|
||||
@@ -28,6 +28,8 @@ import (
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
var isWasmPluginTest = flag.Bool("isWasmPluginTest", false, "")
|
||||
|
||||
func TestHigressConformanceTests(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
@@ -49,27 +51,39 @@ func TestHigressConformanceTests(t *testing.T) {
|
||||
})
|
||||
|
||||
cSuite.Setup(t)
|
||||
var higressTests []suite.ConformanceTest
|
||||
|
||||
higressTests := []suite.ConformanceTest{
|
||||
tests.HTTPRouteSimpleSameNamespace,
|
||||
tests.HTTPRouteHostNameSameNamespace,
|
||||
tests.HTTPRouteRewritePath,
|
||||
tests.HTTPRouteRewriteHost,
|
||||
tests.HTTPRouteCanaryHeader,
|
||||
tests.HTTPRouteEnableCors,
|
||||
tests.HTTPRouteEnableIgnoreCase,
|
||||
tests.HTTPRouteMatchMethods,
|
||||
tests.HTTPRouteMatchQueryParams,
|
||||
tests.HTTPRouteMatchHeaders,
|
||||
tests.HTTPRouteAppRoot,
|
||||
tests.HTTPRoutePermanentRedirect,
|
||||
tests.HTTPRoutePermanentRedirectCode,
|
||||
tests.HTTPRouteTemporalRedirect,
|
||||
tests.HTTPRouteSameHostAndPath,
|
||||
tests.HTTPRouteCanaryHeaderWithCustomizedHeader,
|
||||
tests.HTTPRouteWhitelistSourceRange,
|
||||
tests.HTTPRouteCanaryWeight,
|
||||
tests.HTTPRouteMatchPath,
|
||||
if *isWasmPluginTest {
|
||||
higressTests = []suite.ConformanceTest{
|
||||
tests.WasmPluginsRequestBlock,
|
||||
}
|
||||
} else {
|
||||
higressTests = []suite.ConformanceTest{
|
||||
tests.HTTPRouteSimpleSameNamespace,
|
||||
tests.HTTPRouteHostNameSameNamespace,
|
||||
tests.HTTPRouteRewritePath,
|
||||
tests.HTTPRouteRewriteHost,
|
||||
tests.HTTPRouteCanaryHeader,
|
||||
tests.HTTPRouteEnableCors,
|
||||
tests.HTTPRouteEnableIgnoreCase,
|
||||
tests.HTTPRouteMatchMethods,
|
||||
tests.HTTPRouteMatchQueryParams,
|
||||
tests.HTTPRouteMatchHeaders,
|
||||
tests.HTTPRouteAppRoot,
|
||||
tests.HTTPRoutePermanentRedirect,
|
||||
tests.HTTPRoutePermanentRedirectCode,
|
||||
tests.HTTPRouteTemporalRedirect,
|
||||
tests.HTTPRouteSameHostAndPath,
|
||||
tests.HTTPRouteCanaryHeaderWithCustomizedHeader,
|
||||
tests.HTTPRouteWhitelistSourceRange,
|
||||
tests.HTTPRouteCanaryWeight,
|
||||
tests.HTTPRouteMatchPath,
|
||||
tests.HttpForceRedirectHttps,
|
||||
tests.HttpRedirectAsHttps,
|
||||
tests.HTTPRouteRequestHeaderControl,
|
||||
tests.HTTPRouteDownstreamEncryption,
|
||||
tests.HTTPRouteFullPathRegex,
|
||||
}
|
||||
}
|
||||
|
||||
cSuite.Run(t, higressTests)
|
||||
|
||||
36
tools/hack/build-wasm-plugins.sh
Executable file
36
tools/hack/build-wasm-plugins.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
# 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.
|
||||
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd ./plugins/wasm-go/
|
||||
|
||||
INNER_PLUGIN_NAME=${PLUGIN_NAME-""}
|
||||
if [ ! -n "$INNER_PLUGIN_NAME" ]; then
|
||||
EXTENSIONS_DIR=$(pwd)"/extensions/"
|
||||
echo "build all wasmplugins under folder of $EXTENSIONS_DIR"
|
||||
for file in `ls $EXTENSIONS_DIR`
|
||||
do
|
||||
if [ -d $EXTENSIONS_DIR$file ]; then
|
||||
name=${file##*/}
|
||||
echo "build wasmplugin name of $name"
|
||||
PLUGIN_NAME=${name} make build
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "build wasmplugin name of $INNER_PLUGIN_NAME"
|
||||
PLUGIN_NAME=${INNER_PLUGIN_NAME} make build
|
||||
fi
|
||||
@@ -1,32 +0,0 @@
|
||||
# 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.
|
||||
|
||||
# cluster.conf
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
extraPortMappings:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
hostPort: 443
|
||||
protocol: TCP
|
||||
@@ -20,6 +20,48 @@ set -euo pipefail
|
||||
CLUSTER_NAME=${CLUSTER_NAME:-"higress"}
|
||||
METALLB_VERSION=${METALLB_VERSION:-"v0.13.7"}
|
||||
KIND_NODE_TAG=${KIND_NODE_TAG:-"v1.25.3"}
|
||||
PROJECT_DIR=$(pwd)
|
||||
|
||||
echo ${KIND_NODE_TAG}
|
||||
echo ${CLUSTER_NAME}
|
||||
|
||||
cat <<EOF > "tools/hack/cluster.conf"
|
||||
# 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.
|
||||
|
||||
# cluster.conf
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
extraPortMappings:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
hostPort: 443
|
||||
protocol: TCP
|
||||
extraMounts:
|
||||
- hostPath: ${PROJECT_DIR}/plugins
|
||||
containerPath: /opt/plugins
|
||||
EOF
|
||||
|
||||
## Create kind cluster.
|
||||
if [[ -z "${KIND_NODE_TAG}" ]]; then
|
||||
|
||||
Reference in New Issue
Block a user